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
/*
31
	pfSense_BUILDER_BINARIES:	/usr/local/sbin/relayd
32
	pfSense_MODULE:	routing
33
*/
34

    
35

    
36
/* include all configuration functions */
37

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

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

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

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

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

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

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

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

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

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

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

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

    
107

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

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

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

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

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

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

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

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

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

    
169
	$check_a = array();
170

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

    
194

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

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

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

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

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

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

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

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

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

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

    
302
			for ($j = 0; $j < count($ip_list); $j += 1) {
303
				$ip = $ip_list[$j];
304
				for ($k = 0; $k < count($src_port_array) && $k < count($dest_port_array); $k += 1) {
305
					$src_port = $src_port_array[$k];
306
					$dest_port = $dest_port_array[$k];
307
					if (is_portrange($dest_port)) {
308
						$dest_ports = explode(':', $dest_port);
309
						$dest_port = $dest_ports[0];
310
					}
311

    
312
					$name = $vs_a[$i]['name'];
313
					if ($append_ip_to_name) {
314
						$name .= "_" . $j;
315
					}
316
					if ($append_port_to_name) {
317
						$name .= "_" . str_replace(":", "_", $src_port);
318
					}
319

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

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

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

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

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

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

    
354
						$conf .= "}\n";
355
					}
356
				}
357
			}
358
		}
359
	}
360
	fwrite($fd, $conf);
361
	fclose($fd);
362

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

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

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

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

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

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

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

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

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

    
563
?>
(58-58/65)