Project

General

Profile

Download (17.8 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/* $Id$ */
3
/*
4
	vslb.inc
5
	Copyright (C) 2005-2008 Bill Marquette
6
	All rights reserved.
7

    
8
	Redistribution and use in source and binary forms, with or without
9
	modification, are permitted provided that the following conditions are met:
10

    
11
	1. Redistributions of source code must retain the above copyright notice,
12
	   this list of conditions and the following disclaimer.
13

    
14
	2. Redistributions in binary form must reproduce the above copyright
15
	   notice, this list of conditions and the following disclaimer in the
16
	   documentation and/or other materials provided with the distribution.
17

    
18
	THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
19
	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
20
	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21
	AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
22
	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
	POSSIBILITY OF SUCH DAMAGE.
28

    
29
 */
30

    
31
/*
32
	pfSense_BUILDER_BINARIES:	/usr/local/sbin/relayd
33
	pfSense_MODULE:	routing
34
*/
35

    
36

    
37
/* include all configuration functions */
38

    
39
class Monitor {
40
	private $conf = array();
41
	function __construct($config) {
42
		$this->conf = $config;
43
	}
44

    
45
	public function p() {
46
		return "check {$this->get('proto')}";
47
	}
48
	private function get($var) {
49
		return isset($this->$var) ? $this->$var : "";
50
	}
51
	protected function config($element) {
52
		return isset($this->conf[$element]) ? $this->conf[$element] : "";
53
	}
54
}
55

    
56
class TCPMonitor extends Monitor {
57
	protected $proto = 'tcp';
58
}
59

    
60
class SSLMonitor extends Monitor {
61
	protected $proto = 'ssl';
62
}
63

    
64
class ICMPMonitor extends Monitor {
65
	protected $proto = 'icmp';
66
}
67

    
68
class HTTPMonitor extends Monitor {
69
	protected $proto = 'http';
70
	function __construct($config) {
71
		parent::__construct($config);
72
	}
73
	public function p() {
74
		$method = ($this->code() != "") ? $this->code() : $this->digest();
75
		return "check {$this->proto} {$this->path()} {$this->host()} {$method}";
76
	}
77

    
78
	private function path() {
79
		return $this->config('path') != "" ? "'{$this->config('path')}'" : "";
80
	}
81

    
82
	private function host() {
83
		return $this->config('host') != "" ? "host {$this->config('host')}" : "";
84
	}
85

    
86
	private function code() {
87
		return $this->config('code') != "" ? "code {$this->config('code')}" : "";
88
	}
89

    
90
	private function digest() {
91
		return $this->config('digest') != "" ? "digest {$this->config('digest')}" : "";
92
	}
93
}
94

    
95
class HTTPSMonitor extends HTTPMonitor {
96
	protected $proto = 'https';
97
}
98

    
99
class SendMonitor extends Monitor {
100
	private $proto = 'send';
101
	function __construct($config) {
102
		parent::__construct($config);
103
	}
104
	public function p() {
105
		return "check {$this->proto} {$this->data()} expect {$this->pattern()} {$this->ssl()}";
106
	}
107

    
108

    
109
	private function data() {
110
		return $this->config('send') != "" ? "\"{$this->config('send')}\"" : "\"\"";
111
	}
112

    
113
	private function pattern() {
114
		return $this->config('expect') != "" ? "\"{$this->config('expect')}\"" : "\"\"";
115
	}
116

    
117
	private function ssl() {
118
		return $this->config('ssl') == true ? "ssl" : "";
119
	}
120
}
121

    
122
function echo_lbaction($action) {
123
	global $config;
124

    
125
	// Index actions by name
126
	$actions_a = array();
127
	for ($i=0; isset($config['load_balancer']['lbaction'][$i]); $i++) {
128
		$actions_a[$config['load_balancer']['lbaction'][$i]['name']] = $config['load_balancer']['lbaction'][$i];
129
	}
130

    
131
	$ret = "";
132
	$ret .=  "{$actions_a[$action]['direction']} {$actions_a[$action]['type']} {$actions_a[$action]['action']}";
133
	switch ($actions_a[$action]['action']) {
134
		case 'append':
135
			$ret .= " \"{$actions_a[$action]['options']['value']}\" to \"{$actions_a[$action]['options']['akey']}\"";
136
			break;
137
		case 'change':
138
			$ret .= " \"{$actions_a[$action]['options']['akey']}\" to \"{$actions_a[$action]['options']['value']}\"";
139
			break;
140
		case 'expect':
141
			$ret .= " \"{$actions_a[$action]['options']['value']}\" from \"{$actions_a[$action]['options']['akey']}\"";
142
			break;
143
		case 'filter':
144
			$ret .= " \"{$actions_a[$action]['options']['value']}\" from \"{$actions_a[$action]['options']['akey']}\"";
145
			break;
146
		case 'hash':
147
			$ret .= " \"{$actions_a[$action]['options']['akey']}\"";
148
			break;
149
		case 'log':
150
			$ret .= " \"{$actions_a[$action]['options']['akey']}\"";
151
			break;
152
	}
153
	return $ret;
154
}
155

    
156
function relayd_configure($kill_first=false) {
157
	global $config, $g;
158

    
159
	// have to do this until every call to filter.inc is
160
	// require_once() instead of require().
161
	if (!function_exists('filter_expand_alias_array')) {
162
		require_once("filter.inc");
163
	}
164

    
165
	$vs_a = $config['load_balancer']['virtual_server'];
166
	$pool_a = $config['load_balancer']['lbpool'];
167
	$protocol_a = $config['load_balancer']['lbprotocol'];
168
	$setting = $config['load_balancer']['setting'];
169

    
170
	$check_a = array();
171

    
172
	foreach ((array)$config['load_balancer']['monitor_type'] as $type) {
173
		switch ($type['type']) {
174
			case 'icmp':
175
				$mon = new ICMPMonitor($type['options']);
176
				break;
177
			case 'tcp':
178
				$mon = new TCPMonitor($type['options']);
179
				break;
180
			case 'http':
181
				$mon = new HTTPMonitor($type['options']);
182
				break;
183
			case 'https':
184
				$mon = new HTTPSMonitor($type['options']);
185
				break;
186
			case 'send':
187
				$mon = new SendMonitor($type['options']);
188
				break;
189
		}
190
		if ($mon) {
191
			$check_a[$type['name']] = $mon->p();
192
		}
193
	}
194

    
195

    
196
	$fd = fopen("{$g['varetc_path']}/relayd.conf", "w");
197
	$conf .= "log updates \n";
198

    
199
	/* Global timeout, interval and prefork settings
200
	   if not specified by the user:
201
	   - use a 1000 ms timeout value as in pfsense 2.0.1 and above
202
	   - leave interval and prefork empty, relayd will use its default values */
203

    
204
	if (isset($setting['timeout']) && !empty($setting['timeout'])) {
205
		$conf .= "timeout ".$setting['timeout']." \n";
206
	} else {
207
		$conf .= "timeout 1000 \n";
208
	}
209

    
210
	if (isset($setting['interval']) && !empty($setting['interval'])) {
211
		$conf .= "interval ".$setting['interval']." \n";
212
	}
213

    
214
	if (isset($setting['prefork']) && !empty($setting['prefork'])) {
215
		$conf .= "prefork ".$setting['prefork']." \n";
216
	}
217

    
218
	/* reindex pools by name as we loop through the pools array */
219
	$pools = array();
220
	/* Virtual server pools */
221
	if (is_array($pool_a)) {
222
		for ($i = 0; isset($pool_a[$i]); $i++) {
223
			if (is_array($pool_a[$i]['servers'])) {
224
				if (!empty($pool_a[$i]['retry'])) {
225
					$retrytext = " retry {$pool_a[$i]['retry']}";
226
				} else {
227
					$retrytext = "";
228
				}
229
				$conf .= "table <{$pool_a[$i]['name']}> {\n";
230
				foreach ($pool_a[$i]['servers'] as $server) {
231
					if (is_subnetv4($server)) {
232
						foreach (subnetv4_expand($server) as $ip) {
233
							$conf .= "\t{$ip}{$retrytext}\n";
234
						}
235
					} else {
236
						$conf .= "\t{$server}{$retrytext}\n";
237
					}
238
				}
239
				$conf .= "}\n";
240
				/* Index by name for easier fetching when we loop through the virtual servers */
241
				$pools[$pool_a[$i]['name']] = $pool_a[$i];
242
			}
243
		}
244
	}
245
//  if (is_array($protocol_a)) {
246
//    for ($i = 0; isset($protocol_a[$i]); $i++) {
247
//      $proto = "{$protocol_a[$i]['type']} protocol \"{$protocol_a[$i]['name']}\" {\n";
248
//      if (is_array($protocol_a[$i]['lbaction'])) {
249
//        if ($protocol_a[$i]['lbaction'][0] == "") {
250
//          continue;
251
//        }
252
//        for ($a = 0; isset($protocol_a[$i]['lbaction'][$a]); $a++) {
253
//          $proto .= "  " . echo_lbaction($protocol_a[$i]['lbaction'][$a]) . "\n";
254
//        }
255
//      }
256
//      $proto .= "}\n";
257
//      $conf .= $proto;
258
//    }
259
//  }
260

    
261
	$conf .= "dns protocol \"dnsproto\" {\n";
262
	$conf .= "\t" . "tcp { nodelay, sack, socket buffer 1024, backlog 1000 }\n";
263
	$conf .= "}\n";
264

    
265
	if (is_array($vs_a)) {
266
		for ($i = 0; isset($vs_a[$i]); $i++) {
267

    
268
			$append_port_to_name = false;
269
			if (is_alias($pools[$vs_a[$i]['poolname']]['port'])) {
270
				$dest_port_array = filter_expand_alias_array($pools[$vs_a[$i]['poolname']]['port']);
271
				$append_port_to_name = true;
272
			} else {
273
				$dest_port_array = array($pools[$vs_a[$i]['poolname']]['port']);
274
			}
275
			if (is_alias($vs_a[$i]['port'])) {
276
				$src_port_array = filter_expand_alias_array($vs_a[$i]['port']);
277
				$append_port_to_name = true;
278
			} else if ($vs_a[$i]['port']) {
279
				$src_port_array = array($vs_a[$i]['port']);
280
			} else {
281
				$src_port_array = $dest_port_array;
282
			}
283

    
284
			$append_ip_to_name = false;
285
			if (is_alias($vs_a[$i]['ipaddr'])) {
286
				$ip_list = array();
287
				foreach (filter_expand_alias_array($vs_a[$i]['ipaddr']) as $item) {
288
					log_error("item is $item");
289
					if (is_subnetv4($item)) {
290
						$ip_list = array_merge($ip_list, subnetv4_expand($item));
291
					} else {
292
						$ip_list[] = $item;
293
					}
294
				}
295
				$append_ip_to_name = true;
296
			} else if (is_subnetv4($vs_a[$i]['ipaddr'])) {
297
				$ip_list = subnetv4_expand($vs_a[$i]['ipaddr']);
298
				$append_ip_to_name = true;
299
			} else {
300
				$ip_list = array($vs_a[$i]['ipaddr']);
301
			}
302

    
303
			for ($j = 0; $j < count($ip_list); $j += 1) {
304
				$ip = $ip_list[$j];
305
				for ($k = 0; $k < count($src_port_array) && $k < count($dest_port_array); $k += 1) {
306
					$src_port  = $src_port_array[$k];
307
					$dest_port = $dest_port_array[$k];
308

    
309
					$name = $vs_a[$i]['name'];
310
					if ($append_ip_to_name) {
311
						$name .= "_" . $j;
312
					}
313
					if ($append_port_to_name) {
314
						$name .= "_" . $src_port;
315
					}
316

    
317
					if (($vs_a[$i]['mode'] == 'relay') || ($vs_a[$i]['relay_protocol'] == 'dns')) {
318
						$conf .= "relay \"{$name}\" {\n";
319
						$conf .= "  listen on {$ip} port {$src_port}\n";
320

    
321
						if ($vs_a[$i]['relay_protocol'] == "dns") {
322
							$conf .= "  protocol \"dnsproto\"\n";
323
						} else {
324
							$conf .= "  protocol \"{$vs_a[$i]['relay_protocol']}\"\n";
325
						}
326
						$lbmode = "";
327
						if ($pools[$vs_a[$i]['poolname']]['mode'] == "loadbalance") {
328
							$lbmode = "mode loadbalance";
329
						}
330

    
331
						$conf .= "  forward to <{$vs_a[$i]['poolname']}> port {$dest_port} {$lbmode} {$check_a[$pools[$vs_a[$i]['poolname']]['monitor']]} \n";
332

    
333
						if (isset($vs_a[$i]['sitedown']) &&  strlen($vs_a[$i]['sitedown']) > 0 && ($vs_a[$i]['relay_protocol'] != 'dns')) {
334
							$conf .= "  forward to <{$vs_a[$i]['sitedown']}> port {$dest_port} {$lbmode} {$check_a[$pools[$vs_a[$i]['poolname']]['monitor']]} \n";
335
						}
336
						$conf .= "}\n";
337
					} else  {
338
						$conf .= "redirect \"{$name}\" {\n";
339
						$conf .= "  listen on {$ip} port {$src_port}\n";
340
						$conf .= "  forward to <{$vs_a[$i]['poolname']}> port {$dest_port} {$check_a[$pools[$vs_a[$i]['poolname']]['monitor']]} \n";
341

    
342
						if (isset($config['system']['lb_use_sticky'])) {
343
							$conf .= "  sticky-address\n";
344
						}
345

    
346
						/* sitedown MUST use the same port as the primary pool - sucks, but it's a relayd thing */
347
						if (isset($vs_a[$i]['sitedown']) && strlen($vs_a[$i]['sitedown']) > 0 && ($vs_a[$i]['relay_protocol'] != 'dns')) {
348
							$conf .= "  forward to <{$vs_a[$i]['sitedown']}> port {$dest_port} {$check_a[$pools[$vs_a[$i]['sitedown']]['monitor']]} \n";
349
						}
350

    
351
						$conf .= "}\n";
352
					}
353
				}
354
			}
355
		}
356
	}
357
	fwrite($fd, $conf);
358
	fclose($fd);
359

    
360
	if (is_process_running('relayd')) {
361
		if (! empty($vs_a)) {
362
			if ($kill_first) {
363
				mwexec('pkill relayd');
364
				/* Remove all active relayd anchors now that relayd is no longer running. */
365
				cleanup_lb_anchor("*");
366
				mwexec("/usr/local/sbin/relayd -f {$g['varetc_path']}/relayd.conf");
367
			} else {
368
				// it's running and there is a config, just reload
369
				mwexec("/usr/local/sbin/relayctl reload");
370
			}
371
		} else {
372
			/*
373
			 * XXX: Something breaks our control connection with relayd
374
			 * and makes 'relayctl stop' not work
375
			 * rule reloads are the current suspect
376
			 * mwexec('/usr/local/sbin/relayctl stop');
377
			 *  returns "command failed"
378
			 */
379
			mwexec('pkill relayd');
380
			/* Remove all active relayd anchors now that relayd is no longer running. */
381
			cleanup_lb_anchor("*");
382
		}
383
	} else {
384
		if (!empty($vs_a)) {
385
			// not running and there is a config, start it
386
			/* Remove all active relayd anchors so it can start fresh. */
387
			cleanup_lb_anchor("*");
388
			mwexec("/usr/local/sbin/relayd -f {$g['varetc_path']}/relayd.conf");
389
		}
390
	}
391
}
392

    
393
function get_lb_redirects() {
394
/*
395
# relayctl show summary
396
Id   Type      Name                      Avlblty Status
397
1    redirect  testvs2                           active
398
5    table     test2:80                          active (3 hosts up)
399
11   host      192.168.1.2               91.55%  up
400
10   host      192.168.1.3               100.00% up
401
9    host      192.168.1.4               88.73%  up
402
3    table     test:80                           active (1 hosts up)
403
7    host      192.168.1.2               66.20%  down
404
6    host      192.168.1.3               97.18%  up
405
0    redirect  testvs                            active
406
3    table     test:80                           active (1 hosts up)
407
7    host      192.168.1.2               66.20%  down
408
6    host      192.168.1.3               97.18%  up
409
4    table     testvs-sitedown:80                active (1 hosts up)
410
8    host      192.168.1.4               84.51%  up
411
# relayctl show redirects
412
Id   Type      Name                      Avlblty Status
413
1    redirect  testvs2                           active
414
0    redirect  testvs                            active
415
# relayctl show redirects
416
Id   Type      Name                      Avlblty Status
417
1    redirect  testvs2                           active
418
		   total: 2 sessions
419
		   last: 2/60s 2/h 2/d sessions
420
		   average: 1/60s 0/h 0/d sessions
421
0    redirect  testvs                            active
422
*/
423
	$rdr_a = array();
424
	exec('/usr/local/sbin/relayctl show redirects 2>&1', $rdr_a);
425
	$relay_a = array();
426
	exec('/usr/local/sbin/relayctl show relays 2>&1', $relay_a);
427
	$vs = array();
428
	$cur_entry = "";
429
	for ($i = 0; isset($rdr_a[$i]); $i++) {
430
		$line = $rdr_a[$i];
431
		if (preg_match("/^[0-9]+/", $line)) {
432
			$regs = array();
433
			if ($x = preg_match("/^[0-9]+\s+redirect\s+([^\s]+)\s+([^\s]+)/", $line, $regs)) {
434
				$cur_entry = trim($regs[1]);
435
				$vs[trim($regs[1])] = array();
436
				$vs[trim($regs[1])]['status'] = trim($regs[2]);
437
			}
438
		} elseif (($x = preg_match("/^\s+total:\s(.*)\ssessions/", $line, $regs)) && !empty($cur_entry)) {
439
			$vs[$cur_entry]['total'] = trim($regs[1]);
440
		} elseif (($x = preg_match("/^\s+last:\s(.*)\ssessions/", $line, $regs)) && !empty($cur_entry)) {
441
			$vs[$cur_entry]['last'] = trim($regs[1]);
442
		} elseif (($x = preg_match("/^\s+average:(.*)\ssessions/", $line, $regs)) && !empty($cur_entry)) {
443
			$vs[$cur_entry]['average'] = trim($regs[1]);
444
		}
445
	}
446
	$cur_entry = "";
447
	for ($i = 0; isset($relay_a[$i]); $i++) {
448
		$line = $relay_a[$i];
449
		if (preg_match("/^[0-9]+/", $line)) {
450
			$regs = array();
451
			if ($x = preg_match("/^[0-9]+\s+relay\s+([^\s]+)\s+([^\s]+)/", $line, $regs)) {
452
				$cur_entry = trim($regs[1]);
453
				$vs[trim($regs[1])] = array();
454
				$vs[trim($regs[1])]['status'] = trim($regs[2]);
455
			}
456
		} elseif (($x = preg_match("/^\s+total:\s(.*)\ssessions/", $line, $regs)) && !empty($cur_entry)) {
457
			$vs[$cur_entry]['total'] = trim($regs[1]);
458
		} elseif (($x = preg_match("/^\s+last:\s(.*)\ssessions/", $line, $regs)) && !empty($cur_entry)) {
459
			$vs[$cur_entry]['last'] = trim($regs[1]);
460
		} elseif (($x = preg_match("/^\s+average:(.*)\ssessions/", $line, $regs)) && !empty($cur_entry)) {
461
			$vs[$cur_entry]['average'] = trim($regs[1]);
462
		}
463
	}
464
	return $vs;
465
}
466

    
467
function get_lb_summary() {
468
	$relayctl = array();
469
	exec('/usr/local/sbin/relayctl show summary 2>&1', $relayctl);
470
	$relay_hosts=Array();
471
	foreach ((array) $relayctl as $line) {
472
		$t = explode("\t", $line);
473
		switch (trim($t[1])) {
474
			case "table":
475
				$curpool=trim($t[2]);
476
				break;
477
			case "host":
478
				$curhost=trim($t[2]);
479
				$relay_hosts[$curpool][$curhost]['avail']=trim($t[3]);
480
				$relay_hosts[$curpool][$curhost]['state']=trim($t[4]);
481
				break;
482
		}
483
	}
484
	return $relay_hosts;
485
}
486

    
487
/* Get a list of all relayd virtual server anchors */
488
function get_lb_anchors() {
489
	/* NOTE: These names come back prepended with "relayd/" e.g. "relayd/MyVSName" */
490
	return explode("\n", trim(`/sbin/pfctl -sA -a relayd | /usr/bin/awk '{print $1;}'`));
491
}
492

    
493
/* Remove NAT rules from a relayd anchor that is no longer in use.
494
	$anchorname can either be * to clear all anchors or a specific anchor name.*/
495
function cleanup_lb_anchor($anchorname = "*") {
496
	$lbanchors = get_lb_anchors();
497
	foreach ($lbanchors as $lba) {
498
		if (($anchorname == "*") || ($lba == "relayd/{$anchorname}")) {
499
			/* Flush both the NAT and the Table for the anchor, so it will be completely removed by pf. */
500
			mwexec("/sbin/pfctl -a " . escapeshellarg($lba) . " -F nat");
501
			mwexec("/sbin/pfctl -a " . escapeshellarg($lba) . " -F Tables");
502
		}
503
	}
504
}
505

    
506
/* Mark an anchor for later cleanup. This will allow us to remove an old VS name */
507
function cleanup_lb_mark_anchor($name) {
508
	global $g;
509
	/* Nothing to do! */
510
	if (empty($name)) {
511
		return;
512
	}
513
	$filename = "{$g['tmp_path']}/relayd_anchors_remove";
514
	$cleanup_anchors = array();
515
	/* Read in any currently unapplied name changes */
516
	if (file_exists($filename)) {
517
		$cleanup_anchors = explode("\n", file_get_contents($filename));
518
	}
519
	/* Only add the anchor to the list if it's not already there. */
520
	if (!in_array($name, $cleanup_anchors)) {
521
		$cleanup_anchors[] = $name;
522
	}
523
	file_put_contents($filename, implode("\n", $cleanup_anchors));
524
}
525

    
526
/* Cleanup relayd anchors that have been marked for cleanup. */
527
function cleanup_lb_marked() {
528
	global $g, $config;
529
	$filename = "{$g['tmp_path']}/relayd_anchors_remove";
530
	$cleanup_anchors = array();
531
	/* Nothing to do! */
532
	if (!file_exists($filename)) {
533
		return;
534
	} else {
535
		$cleanup_anchors = explode("\n", file_get_contents($filename));
536
		/* Nothing to do! */
537
		if (empty($cleanup_anchors)) {
538
			return;
539
		}
540
	}
541

    
542
	/* Load current names so we can make sure we don't remove an anchor that is still in use. */
543
	$vs_a = $config['load_balancer']['virtual_server'];
544
	$active_vsnames = array();
545
	if (is_array($vs_a)) {
546
		foreach ($vs_a as $vs) {
547
			$active_vsnames[] = $vs['name'];
548
		}
549
	}
550

    
551
	foreach ($cleanup_anchors as $anchor) {
552
		/* Only cleanup an anchor if it is not still active. */
553
		if (!in_array($anchor, $active_vsnames)) {
554
			cleanup_lb_anchor($anchor);
555
		}
556
	}
557
	unlink_if_exists($filename);
558
}
559

    
560
?>
(60-60/68)