Project

General

Profile

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

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

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

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

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

    
28
 */
29

    
30
/* include all configuration functions */
31

    
32
class Monitor {
33
	private $conf = array();
34
	function __construct($config) {
35
		$this->conf = $config;
36
	}
37

    
38
	public function p() {
39
		return "check {$this->get('proto')}";
40
	}
41
	private function get($var) {
42
		return isset($this->$var) ? $this->$var : "";
43
	}
44
	protected function config($element) {
45
		return isset($this->conf[$element]) ? $this->conf[$element] : "";
46
	}
47
}
48

    
49
class TCPMonitor extends Monitor {
50
	protected $proto = 'tcp';
51
}
52

    
53
class SSLMonitor extends Monitor {
54
	protected $proto = 'ssl';
55
}
56

    
57
class ICMPMonitor extends Monitor {
58
	protected $proto = 'icmp';
59
}
60

    
61
class HTTPMonitor extends Monitor {
62
	protected $proto = 'http';
63
	function __construct($config) {
64
		parent::__construct($config);
65
	}
66
	public function p() {
67
		$method = ($this->code() != "") ? $this->code() : $this->digest();
68
		return "check {$this->proto} {$this->path()} {$this->host()} {$method}";
69
	}
70

    
71
	private function path() {
72
		return $this->config('path') != "" ? "'{$this->config('path')}'" : "";
73
	}
74

    
75
	private function host() {
76
		return $this->config('host') != "" ? "host {$this->config('host')}" : "";
77
	}
78

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

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

    
88
class HTTPSMonitor extends HTTPMonitor {
89
	protected $proto = 'https';
90
}
91

    
92
class SendMonitor extends Monitor {
93
	private $proto = 'send';
94
	function __construct($config) {
95
		parent::__construct($config);
96
	}
97
	public function p() {
98
		return "check {$this->proto} {$this->data()} expect {$this->pattern()} {$this->ssl()}";
99
	}
100

    
101

    
102
	private function data() {
103
		return $this->config('send') != "" ? "\"{$this->config('send')}\"" : "\"\"";
104
	}
105

    
106
	private function pattern() {
107
		return $this->config('expect') != "" ? "\"{$this->config('expect')}\"" : "\"\"";
108
	}
109

    
110
	private function ssl() {
111
		return $this->config('ssl') == true ? "ssl" : "";
112
	}
113
}
114

    
115
function echo_lbaction($action) {
116
	global $config;
117

    
118
	// Index actions by name
119
	$actions_a = array();
120
	for ($i = 0; isset($config['load_balancer']['lbaction'][$i]); $i++) {
121
		$actions_a[$config['load_balancer']['lbaction'][$i]['name']] = $config['load_balancer']['lbaction'][$i];
122
	}
123

    
124
	$ret = "";
125
	$ret .= "{$actions_a[$action]['direction']} {$actions_a[$action]['type']} {$actions_a[$action]['action']}";
126
	switch ($actions_a[$action]['action']) {
127
		case 'append':
128
			$ret .= " \"{$actions_a[$action]['options']['value']}\" to \"{$actions_a[$action]['options']['akey']}\"";
129
			break;
130
		case 'change':
131
			$ret .= " \"{$actions_a[$action]['options']['akey']}\" to \"{$actions_a[$action]['options']['value']}\"";
132
			break;
133
		case 'expect':
134
			$ret .= " \"{$actions_a[$action]['options']['value']}\" from \"{$actions_a[$action]['options']['akey']}\"";
135
			break;
136
		case 'filter':
137
			$ret .= " \"{$actions_a[$action]['options']['value']}\" from \"{$actions_a[$action]['options']['akey']}\"";
138
			break;
139
		case 'hash':
140
			$ret .= " \"{$actions_a[$action]['options']['akey']}\"";
141
			break;
142
		case 'log':
143
			$ret .= " \"{$actions_a[$action]['options']['akey']}\"";
144
			break;
145
	}
146
	return $ret;
147
}
148

    
149
function relayd_configure($kill_first=false) {
150
	global $config, $g;
151

    
152
	// have to do this until every call to filter.inc is
153
	// require_once() instead of require().
154
	if (!function_exists('filter_expand_alias_array')) {
155
		require_once("filter.inc");
156
	}
157

    
158
	$vs_a = $config['load_balancer']['virtual_server'];
159
	$pool_a = $config['load_balancer']['lbpool'];
160
	$protocol_a = $config['load_balancer']['lbprotocol'];
161
	$setting = $config['load_balancer']['setting'];
162

    
163
	$check_a = array();
164

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

    
188

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

    
192
	/* Global timeout, interval and prefork settings
193
	   if not specified by the user:
194
	   - use a 1000 ms timeout value as in pfsense 2.0.1 and above
195
	   - leave interval and prefork empty, relayd will use its default values */
196

    
197
	if (isset($setting['timeout']) && !empty($setting['timeout'])) {
198
		$conf .= "timeout ".$setting['timeout']." \n";
199
	} else {
200
		$conf .= "timeout 1000 \n";
201
	}
202

    
203
	if (isset($setting['interval']) && !empty($setting['interval'])) {
204
		$conf .= "interval ".$setting['interval']." \n";
205
	}
206

    
207
	if (isset($setting['prefork']) && !empty($setting['prefork'])) {
208
		$conf .= "prefork ".$setting['prefork']." \n";
209
	}
210

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

    
254
	$conf .= "dns protocol \"dnsproto\" {\n";
255
	$conf .= "\t" . "tcp { nodelay, sack, socket buffer 1024, backlog 1000 }\n";
256
	$conf .= "}\n";
257

    
258
	if (is_array($vs_a)) {
259
		for ($i = 0; isset($vs_a[$i]); $i++) {
260

    
261
			$append_port_to_name = false;
262
			if (is_alias($pools[$vs_a[$i]['poolname']]['port'])) {
263
				$dest_port_array = filter_expand_alias_array($pools[$vs_a[$i]['poolname']]['port']);
264
				$append_port_to_name = true;
265
			} else {
266
				$dest_port_array = array($pools[$vs_a[$i]['poolname']]['port']);
267
			}
268
			if (is_alias($vs_a[$i]['port'])) {
269
				$src_port_array = filter_expand_alias_array($vs_a[$i]['port']);
270
				$append_port_to_name = true;
271
			} else if ($vs_a[$i]['port']) {
272
				$src_port_array = array($vs_a[$i]['port']);
273
			} else {
274
				$src_port_array = $dest_port_array;
275
			}
276

    
277
			$append_ip_to_name = false;
278
			if (is_alias($vs_a[$i]['ipaddr'])) {
279
				$ip_list = array();
280
				foreach (filter_expand_alias_array($vs_a[$i]['ipaddr']) as $item) {
281
					log_error("item is $item");
282
					if (is_subnetv4($item)) {
283
						$ip_list = array_merge($ip_list, subnetv4_expand($item));
284
					} else {
285
						$ip_list[] = $item;
286
					}
287
				}
288
				$append_ip_to_name = true;
289
			} else if (is_subnetv4($vs_a[$i]['ipaddr'])) {
290
				$ip_list = subnetv4_expand($vs_a[$i]['ipaddr']);
291
				$append_ip_to_name = true;
292
			} else {
293
				$ip_list = array($vs_a[$i]['ipaddr']);
294
			}
295

    
296
			for ($j = 0; $j < count($ip_list); $j += 1) {
297
				$ip = $ip_list[$j];
298
				for ($k = 0; $k < count($src_port_array) && $k < count($dest_port_array); $k += 1) {
299
					$src_port = $src_port_array[$k];
300
					$dest_port = $dest_port_array[$k];
301
					if (is_portrange($dest_port)) {
302
						$dest_ports = explode(':', $dest_port);
303
						$dest_port = $dest_ports[0];
304
					}
305

    
306
					$name = $vs_a[$i]['name'];
307
					if ($append_ip_to_name) {
308
						$name .= "_" . $j;
309
					}
310
					if ($append_port_to_name) {
311
						$name .= "_" . str_replace(":", "_", $src_port);
312
					}
313

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

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

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

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

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

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

    
348
						$conf .= "}\n";
349
					}
350
				}
351
			}
352
		}
353
	}
354
	fwrite($fd, $conf);
355
	fclose($fd);
356

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

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

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

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

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

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

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

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

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

    
557
?>
(58-58/65)