net/pfSense-pkg-haproxy: use the XMLRPC plugins for HA sync. Implement #16231

This commit is contained in:
Marcos Mendoza
2025-06-06 17:34:14 -06:00
parent 2f02dbed6a
commit abe71f46f5
6 changed files with 66 additions and 269 deletions
+1 -6
View File
@@ -1,8 +1,7 @@
# $FreeBSD$
PORTNAME= pfSense-pkg-haproxy
PORTVERSION= 0.63
PORTREVISION= 10
PORTVERSION= 0.64
CATEGORIES= net
MASTER_SITES= # empty
DISTFILES= # empty
@@ -42,14 +41,10 @@ do-install:
${STAGEDIR}${PREFIX}/pkg/haproxy
${INSTALL_DATA} -m 0755 ${FILESDIR}${PREFIX}/pkg/haproxy/haproxy_socket.sh \
${STAGEDIR}${PREFIX}/pkg/haproxy
${INSTALL_DATA} -m 0755 ${FILESDIR}${PREFIX}/pkg/haproxy/haproxy_sync.sh \
${STAGEDIR}${PREFIX}/pkg/haproxy
${INSTALL_DATA} ${FILESDIR}${PREFIX}/pkg/haproxy/haproxy_gui.inc \
${STAGEDIR}${PREFIX}/pkg/haproxy
${INSTALL_DATA} ${FILESDIR}${PREFIX}/pkg/haproxy/haproxy_socketinfo.inc \
${STAGEDIR}${PREFIX}/pkg/haproxy
${INSTALL_DATA} ${FILESDIR}${PREFIX}/pkg/haproxy/haproxy_xmlrpcsyncclient.inc \
${STAGEDIR}${PREFIX}/pkg/haproxy
${INSTALL_DATA} ${FILESDIR}${PREFIX}/pkg/haproxy/haproxy_htmllist.inc \
${STAGEDIR}${PREFIX}/pkg/haproxy
${INSTALL_DATA} ${FILESDIR}${PREFIX}/pkg/haproxy/haproxy_utils.inc \
@@ -54,6 +54,15 @@
<item>
<type>plugin_certificates</type>
</item>
<item>
<type>plugin_xmlrpc_send</type>
</item>
<item>
<type>plugin_xmlrpc_recv</type>
</item>
<item>
<type>plugin_xmlrpc_recv_done</type>
</item>
</plugins>
<configpath>installedpackages->haproxy->config</configpath>
<custom_php_install_command>
@@ -27,7 +27,6 @@ require_once("notices.inc");
require_once("filter.inc");
require_once("haproxy_utils.inc");
require_once("haproxy_socketinfo.inc");
require_once("haproxy_xmlrpcsyncclient.inc");
$d_haproxyconfdirty_path = $g['varrun_path'] . "/haproxy.conf.dirty";
#region Global haproxy array item definitions..
@@ -1372,9 +1371,6 @@ function haproxy_check_and_run(&$messages, $reload) {
rmdir_recursive($testpath);
$ok = haproxy_check_run(1) == 0;
$messages = $haproxy_run_message;
if ($ok && config_path_enabled('installedpackages/haproxy', 'enablesync')) {
mwexec_bg("/usr/local/pkg/haproxy/haproxy_sync.sh");
}
}
return $ok;
}
@@ -2332,17 +2328,8 @@ function load_ipfw_rules() {
$transparent_interfaces[$interface] = 1;
}
if (haproxy_utils::$pf_version < 2.4) {
$rulenum = 64000; // why that high? captiveportal.inc also does it...
// pfSense 2.2 FreeBSD 10
mwexec("/sbin/ipfw zone $ipfw_zone_haproxy create", true);
foreach($transparent_interfaces as $transparent_if => $value) {
mwexec("/sbin/ipfw zone $ipfw_zone_haproxy madd $transparent_if", true);
}
} else {
// pfSense 2.4, captive portal rules start at 1000
$rulenum = 10;
}
// pfSense 2.4, captive portal rules start at 1000
$rulenum = 10;
foreach($transparent_backends as $transparent_be) {
if (is_ipaddrv4($transparent_be["address"])) {
@@ -2357,11 +2344,7 @@ function load_ipfw_rules() {
file_put_contents("{$g['tmp_path']}/ipfw_{$ipfw_zone_haproxy}.haproxy.rules", $rules);
if (haproxy_utils::$pf_version < 2.4) {
mwexec("/sbin/ipfw -x $ipfw_zone_haproxy -q {$g['tmp_path']}/ipfw_{$ipfw_zone_haproxy}.haproxy.rules", true);
} else {
mwexec("/sbin/ipfw -q {$g['tmp_path']}/ipfw_{$ipfw_zone_haproxy}.haproxy.rules", true);
}
mwexec("/sbin/ipfw -q {$g['tmp_path']}/ipfw_{$ipfw_zone_haproxy}.haproxy.rules", true);
}
function delete_ipfw_rules() {
@@ -2619,48 +2602,64 @@ function killprocesses($processname, $pidfile, $signal = "KILL") {
exec("kill -$signal `pgrep -x $processname | grep -w -f $pidfile`");
}
function haproxy_sync_xmlrpc_settings() {
if (!is_array(config_get_path('installedpackages/haproxysyncpkg'))) {
return false;
function haproxy_plugin_xmlrpc_send() {
if (!config_path_enabled('installedpackages/haproxy', 'enablesync')) {
return [];
}
// preserve 'old' sync settings, that should not be overwritten by xmlrpc-sync.
$old_config = config_get_path('installedpackages/haproxy', []);
$enable = config_path_enabled('installedpackages/haproxy', 'enablesync');
config_set_path('installedpackages/haproxy', config_get_path('installedpackages/haproxysyncpkg'));
config_del_path('installedpackages/haproxysyncpkg');
// restore 'old' settings.
config_set_path('installedpackages/haproxy/enablesync', $enable ? true : false);
config_set_path('installedpackages/haproxy/log-send-hostname', $old_config['log-send-hostname']);
write_config("haproxy, xmlrpc config synced"); // Write new 'merged' configuration
return [
'installedpackages/haproxy'
];
}
function haproxy_do_xmlrpc_sync() {
$syncinfo = array();
$syncinfo['sync_logname'] = "HAProxy";
$syncinfo['data'] = haproxy_xmlrpc_sync_prepare_config();
$syncinfo['sync_include'] = "/usr/local/pkg/haproxy/haproxy.inc";
$syncinfo['sync_done_execute'] = "haproxy_xmlrpc_sync_configure";
xmlrpc_sync_execute($syncinfo);
}
function haproxy_xmlrpc_sync_prepare_config() {
/* xml will hold the sections to sync */
$xml = array();
$xml['haproxysyncpkg'] = config_get_path('installedpackages/haproxy');
return $xml;
}
function haproxy_xmlrpc_sync_configure() {
// this function is called by xmlrpc after config has been synced.
haproxy_sync_xmlrpc_settings();
haproxy_configure(); // Configure HAProxy config files to use the new configuration.
// sync 2nd and further nodes in the chain if applicable.
if (config_path_enabled('installedpackages/haproxy', 'enablesync')) {
haproxy_do_xmlrpc_sync();
function haproxy_plugin_xmlrpc_recv($new_sections) {
if (!array_path_enabled($new_sections, 'installedpackages/haproxy', 'enablesync')) {
return [];
}
$section_paths = [
'installedpackages/haproxy'
];
$ret = [
'xmlrpc_recv_result' => false
];
foreach ($section_paths as $path) {
$old_section = config_get_path($path, []);
$new_section = array_get_path($new_sections, $path, []);
// Don't save empty configuration sections.
if (empty($old_section) && empty($new_section)) {
continue;
}
// Ignore unchanged configuration sections and sync-related package settings.
if ($path == 'installedpackages/haproxy') {
$old_section_temp = $old_section;
$new_section_temp = $new_section;
unset($old_section_temp['enablesync'], $old_section_temp['log-send-hostname']);
unset($new_section_temp['enablesync'], $new_section_temp['log-send-hostname']);
if ($old_section_temp === $new_section_temp) {
continue;
} else {
// Retain existing sync-related package settings.
unset($new_section['enablesync'], $new_section['log-send-hostname']);
}
} elseif ($old_section === $new_section) {
continue;
}
$ret[$path] = array_merge($old_section, $new_section);
$ret['xmlrpc_recv_result'] = true;
}
return $ret;
}
function haproxy_plugin_xmlrpc_recv_done($xmlrpc_recv_result) {
if (array_get_path($xmlrpc_recv_result, 'haproxy/xmlrpc_recv_result') !== true) {
return;
}
haproxy_configure();
}
function get_frontend_id($name) {
@@ -1,27 +0,0 @@
#!/usr/local/bin/php -f
<?php
/*
* haproxy_sync.sh
*
* part of pfSense (https://www.pfsense.org)
* Copyright (c) 2016-2025 Rubicon Communications, LLC (Netgate)
* Copyright (c) 2016 PiBa-NL
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
require_once('haproxy.inc');
echo "HAProxy Start sync ". (new DateTime('NOW'))->format('c') . "\n";
haproxy_do_xmlrpc_sync();
echo "HAProxy Stop sync ". (new DateTime('NOW'))->format('c') . "\n";
@@ -1,177 +0,0 @@
<?php
/*
* haproxy_xmlrpcsyncclient.inc
*
* part of pfSense (https://www.pfsense.org)
* Copyright (c) 2009-2025 Rubicon Communications, LLC (Netgate)
* Copyright (c) 2013 PiBa-NL
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* include all configuration functions */
require_once("functions.inc");
require_once("pkg-utils.inc");
require_once("notices.inc");
if(!function_exists('pf_version')) {
function pf_version() {
return substr(trim(file_get_contents("/etc/version")), 0, 3);
}
}
function xmlrpc_sync_execute($syncinfo) {
// name that is logged if something fails during syncing
$sync_logname = $syncinfo['sync_logname'];
// configuration data to sync
$xml = $syncinfo['data'];
// include file in which the "function sync_done_execute(){xxx}" must be pressent
$sync_include = $syncinfo['sync_include'];
// executes to apply the changed configuration on the target system
$sync_function = $syncinfo['sync_done_execute'];
if (pf_version() < "2.4") {
xmlrpc_sync_execute_23($sync_logname, $xml, $sync_include, $sync_function);
} else {
xmlrpc_sync_execute_24($sync_logname, $xml, $sync_include, $sync_function);
}
}
function xmlrpc_sync_execute_24($sync_logname, $xml, $sync_include,$sync_function) {
if (empty(config_get_path('hasync/synchronizetoip'))) {
syslog(LOG_NOTICE, "haproxy: cannot sync configuration (no ip/host configured to sync to on this machine), disable sync to avoid this message.");
return;
}
require_once("xmlrpc_client.inc");
$rpc_client = new pfsense_xmlrpc_client();
$resp = $rpc_client->xmlrpc_method('merge_installedpackages_section', $xml);
$execcmd = "require_once('{$sync_include}');\n";
$execcmd .= "{$sync_function}();\n";
$resp = $rpc_client->xmlrpc_exec_php($execcmd);
}
function xmlrpc_sync_execute_23($sync_logname, $xml, $sync_include,$sync_function) {
global $g;
//if(!$password)
$password = config_get_path('hasync/password');
if(!$password) {
return;
}
//if(!$sync_to_ip)
$sync_to_ip = config_get_path('hasync/synchronizetoip');
if(!$sync_to_ip) {
return;
}
// Do not allow syncing to self.
$donotsync = false;
$localips = get_configured_ip_addresses();
if (in_array($sync_to_ip, $localips , true)) {
$donotsync = true;
}
if($donotsync) {
log_error("Disallowing sync loop for {$sync_logname} sync.");
return;
}
require_once("xmlrpc.inc");
require_once("xmlrpc_client.inc");
//$xmlrpc_sync_neighbor = $sync_to_ip;
if(config_get_path('system/webgui/protocol') != "") {
$synchronizetoip = config_get_path('system/webgui/protocol');
$synchronizetoip .= "://";
}
$port = config_get_path('system/webgui/port');
/* if port is empty lets rely on the protocol selection */
if($port == "") {
if(config_get_path('system/webgui/protocol') == "http") {
$port = "80";
} else {
$port = "443";
}
}
$synchronizetoip .= $sync_to_ip;
/* xml will hold the sections to sync */
//$xml = prepare_xmlrpc_sync_config();
/* assemble xmlrpc payload */
$params = array(
XML_RPC_encode($password),
XML_RPC_encode($xml)
);
/* set a few variables needed for sync code borrowed from filter.inc */
$url = $synchronizetoip;
log_error("Beginning {$sync_logname} XMLRPC sync to {$url}:{$port}.");
$method = 'pfsense.merge_installedpackages_section_xmlrpc';
$msg = new XML_RPC_Message($method, $params);
$cli = new XML_RPC_Client('/xmlrpc.php', $url, $port);
$cli->setCredentials('admin', $password);
if($g['debug']) {
$cli->setDebug(1);
}
/* send our XMLRPC message and timeout after 250 seconds */
$resp = $cli->send($msg, "250");
if(!$resp) {
$error = "A communications error occurred while attempting {$sync_logname} XMLRPC sync with {$url}:{$port}.";
log_error($error);
file_notice("sync_settings", $error, "{$sync_logname} Settings Sync", "");
} elseif($resp->faultCode()) {
$cli->setDebug(1);
$resp = $cli->send($msg, "250");
$error = "An error code was received while attempting {$sync_logname} XMLRPC sync with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString();
log_error($error);
file_notice("sync_settings", $error, "{$sync_logname} Settings Sync", "");
} else {
log_error("{$sync_logname} XMLRPC sync successfully completed with {$url}:{$port}.");
}
/* tell package to reload our settings on the destination sync host. */
$method = 'pfsense.exec_php';
$execcmd = "require_once('{$sync_include}');\n";
$execcmd .= "{$sync_function}();\n";
/* assemble xmlrpc payload */
$params = array(
XML_RPC_encode($password),
XML_RPC_encode($execcmd)
);
log_error("{$sync_logname} XMLRPC reload data {$url}:{$port}.");
$msg = new XML_RPC_Message($method, $params);
$cli = new XML_RPC_Client('/xmlrpc.php', $url, $port);
$cli->setCredentials('admin', $password);
$resp = $cli->send($msg, "250");
if(!$resp) {
$error = "A communications error occurred while attempting {$sync_logname} XMLRPC sync with {$url}:{$port} (exec_php).";
log_error($error);
file_notice("sync_settings", $error, "{$sync_logname} Settings Reload", "");
} elseif($resp->faultCode()) {
$cli->setDebug(1);
$resp = $cli->send($msg, "250");
$error = "An error code was received while attempting {$sync_logname} XMLRPC sync with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString();
log_error($error);
file_notice("sync_settings", $error, "{$sync_logname} Settings Sync", "");
} else {
log_error("{$sync_logname} XMLRPC reload data success with {$url}:{$port} (exec_php).");
}
}
-2
View File
@@ -2,11 +2,9 @@ pkg/haproxy.xml
pkg/haproxy/haproxy.inc
pkg/haproxy/haproxy_gui.inc
pkg/haproxy/haproxy_socketinfo.inc
pkg/haproxy/haproxy_xmlrpcsyncclient.inc
pkg/haproxy/haproxy_htmllist.inc
pkg/haproxy/haproxy_utils.inc
pkg/haproxy/haproxy_socket.sh
pkg/haproxy/haproxy_sync.sh
pkg/haproxy/pkg_haproxy_tabs.inc
pkg/haproxy/haproxy_upgrade_config.inc
www/haproxy/haproxy_listeners.php