Project

General

Profile

Download (17.9 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
					if (is_portrange($dest_port)) {
309
						$dest_ports = explode(':', $dest_port);
310
						$dest_port = $dest_ports[0];
311
					}
312

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
564
?>
(60-60/67)