Repository: pfsense/FreeBSD-ports
Pull request #1391: Add mDNS Bridge (mdns-bridge) package
Author: login: dennypage
Created: 2024-11-19T20:15:48Z | Merged: 2025-03-12T17:07:38Z

Implements Redmine #15853 (https://redmine.pfsense.org/issues/15853)

mDNS Bridge is intended as an alternative to Avahi reflection for sharing Multicast DNS across network interfaces.

Compared with Avahi, mDNS Bridge is expected to offer several advantages:

- mDNS Bridge is purpose built for bridging mDNS data only.
- Does not retain state across packets (no caching).
- Correctly forwards mDNS NSEC packets.
- Supports newer mDNS packet types such as OPT, SVCB and HTTPS.
- Offers interface specific inbound/outbound filter controls.
- Smaller and simpler code, offering easier inspection/validation.
- Run time memory requirements approximately 1/2 the size of Avahi.


Diff:
diff --git a/net/pfSense-pkg-mDNS-Bridge/Makefile b/net/pfSense-pkg-mDNS-Bridge/Makefile
new file mode 100644
index 000000000000..f42af474d0b6
--- /dev/null
+++ b/net/pfSense-pkg-mDNS-Bridge/Makefile
@@ -0,0 +1,46 @@
+
+PORTNAME=	pfSense-pkg-mDNS-Bridge
+PORTVERSION=	1.0
+CATEGORIES=	net
+MASTER_SITES=	# empty
+DISTFILES=	# empty
+
+MAINTAINER=	dennypage@me.com
+COMMENT=	pfSense package mDNS Bridge
+WWW=		https://github.com/dennypage/mdns-bridge
+
+LICENSE=	APACHE20
+
+RUN_DEPENDS=	mdns-bridge>=1.0.1:net/mdns-bridge
+
+NO_BUILD=	yes
+NO_MTREE=	yes
+
+SUB_FILES=	pkg-install pkg-deinstall
+SUB_LIST=	PORTNAME=${PORTNAME}
+
+do-extract:
+	${MKDIR} ${WRKSRC}
+
+do-install:
+	${MKDIR} ${STAGEDIR}${PREFIX}/pkg
+	${MKDIR} ${STAGEDIR}/etc/inc/priv
+	${MKDIR} ${STAGEDIR}${PREFIX}/www/shortcuts
+	${MKDIR} ${STAGEDIR}${DATADIR}
+	${INSTALL_DATA} ${FILESDIR}${PREFIX}/pkg/mdns_bridge.xml \
+		${STAGEDIR}${PREFIX}/pkg
+	${INSTALL_DATA} ${FILESDIR}${PREFIX}/pkg/mdns-bridge.inc \
+		${STAGEDIR}${PREFIX}/pkg
+	${INSTALL_DATA} ${FILESDIR}/etc/inc/priv/mdns-bridge.priv.inc \
+		${STAGEDIR}/etc/inc/priv
+	${INSTALL_DATA} ${FILESDIR}${DATADIR}/info.xml \
+		${STAGEDIR}${DATADIR}
+	${INSTALL_DATA} ${FILESDIR}${PREFIX}/www/shortcuts/pkg_mdns-bridge.inc \
+		${STAGEDIR}${PREFIX}/www/shortcuts
+	${INSTALL_DATA} ${FILESDIR}${PREFIX}/www/mdns-bridge.php \
+		${STAGEDIR}${PREFIX}/www
+
+	@${REINPLACE_CMD} -i '' -e "s|%%PKGVERSION%%|${PKGVERSION}|" \
+		${STAGEDIR}${DATADIR}/info.xml
+
+.include <bsd.port.mk>
diff --git a/net/pfSense-pkg-mDNS-Bridge/files/etc/inc/priv/mdns-bridge.priv.inc b/net/pfSense-pkg-mDNS-Bridge/files/etc/inc/priv/mdns-bridge.priv.inc
new file mode 100644
index 000000000000..c1568d474d43
--- /dev/null
+++ b/net/pfSense-pkg-mDNS-Bridge/files/etc/inc/priv/mdns-bridge.priv.inc
@@ -0,0 +1,31 @@
+<?php
+/*
+ * mdns-bridge.priv.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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.
+ */
+
+global $priv_list;
+
+$priv_list['page-services-mdns-bridge'] = array();
+$priv_list['page-services-mdns-bridge']['name'] = "WebCfg - Services: mDNS Bridge";
+$priv_list['page-services-mdns-bridge']['descr'] = "Access the mDNS Bridge package GUI";
+
+$priv_list['page-services-mdns-bridge']['match'] = array();
+$priv_list['page-services-mdns-bridge']['match'][] = "mdns-bridge.php*";
+
+?>
diff --git a/net/pfSense-pkg-mDNS-Bridge/files/pkg-deinstall.in b/net/pfSense-pkg-mDNS-Bridge/files/pkg-deinstall.in
new file mode 100644
index 000000000000..83d1c3dcab9e
--- /dev/null
+++ b/net/pfSense-pkg-mDNS-Bridge/files/pkg-deinstall.in
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+/usr/local/bin/php -f /etc/rc.packages %%PORTNAME%% ${2}
diff --git a/net/pfSense-pkg-mDNS-Bridge/files/pkg-install.in b/net/pfSense-pkg-mDNS-Bridge/files/pkg-install.in
new file mode 100644
index 000000000000..c192d05db62d
--- /dev/null
+++ b/net/pfSense-pkg-mDNS-Bridge/files/pkg-install.in
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+if [ "${2}" != "POST-INSTALL" ]; then
+	exit 0
+fi
+
+${PKG_ROOTDIR}/usr/local/bin/php -f ${PKG_ROOTDIR}/etc/rc.packages %%PORTNAME%% ${2}
diff --git a/net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc b/net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
new file mode 100644
index 000000000000..dd6340c55c8c
--- /dev/null
+++ b/net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
@@ -0,0 +1,259 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
+}
+
+// Get the CARP VHID in use by mDNS Bridge
+function mdns_bridge_get_carp_vhid() {
+	$carp_vhid = config_get_path('installedpackages/mdns-bridge/carp_vhid', 'none');
+	if ($carp_vhid != 'none') {
+		return $carp_vhid;
+	}
+	return null;
+}
+
+// Get a CARP VHID list for use in mdns_bridge.php
+function mdns_bridge_get_carp_list() {
+	$list = array('none' => 'none');
+	foreach (get_configured_vip_list('all', VIP_CARP) as $vid => $vaddr) {
+		$vip = get_configured_vip($vid);
+		$list[$vid] = sprintf("VHID %s on %s (%s)", $vip['vhid'], convert_friendly_interface_to_friendly_descr($vip['interface']), $vaddr);
+	}
+	return ($list);
+}
+
+// Write the config and rc files
+function mdns_bridge_write_files() {
+	// Get the current configuration
+	$current_config = config_get_path('installedpackages/mdns-bridge', []);
+
+	// Get the real interface names
+	$interfaces = array();
+	foreach (explode(',', array_get_path($current_config, 'active_interfaces', '')) as $interface) {
+		$interfaces[$interface] = get_real_interface($interface);
+	}
+
+	// Global section
+	$file_array = array();
+	$file_array[] = '# This file is generated by the pfSense mDNS Bridge package.';
+	$file_array[] = '# Do not edit this file, it will be overwritten automatically.';
+
+	$file_array[] = '[global]';
+	$file_array[] = 'interfaces = ' . implode(', ', $interfaces);
+
+	$ip_protocols = array_get_path($current_config, 'global_ip_protocols');
+	switch ($ip_protocols) {
+		case 'ipv4':
+			$file_array['global_ip_protocols'] = 'disable-ipv6 = yes';
+			break;
+		case 'ipv6':
+			$file_array['global_ip_protocols'] = 'disable-ipv4 = yes';
+			break;
+	}
+
+	$filter_type = array_get_path($current_config, 'global_filter_type');
+	$filter_list = array_get_path($current_config, 'global_filter_list');
+	switch ($filter_type) {
+		case 'allow':
+			$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+			break;
+		case 'deny':
+			$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+			break;
+		default:
+			$disable_packet_filtering = array_get_path($current_config, 'disable_packet_filtering');
+			if ($disable_packet_filtering)
+			{
+				$file_array['disable-packet-filtering'] = 'disable-packet-filtering = yes';
+			}
+	}
+
+
+	// Interface sections
+	foreach ($interfaces as $interface => $real_interface) {
+		$file_array[] = '';
+		$file_array[] = "[{$real_interface}]";
+
+		if (!$file_array['global_ip_protocols']) {
+			$ip_protocols = array_get_path($current_config, "interfaces/{$interface}/ip_protocols");
+			switch ($ip_protocols) {
+				case 'ipv4':
+					$file_array[] = 'disable-ipv6 = yes';
+					break;
+				case 'ipv6':
+					$file_array[] = 'disable-ipv4 = yes';
+					break;
+			}
+		}
+
+		$filter_type = array_get_path($current_config, "interfaces/{$interface}/inbound_filter_type");
+		$filter_list = array_get_path($current_config, "interfaces/{$interface}/inbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+
+		$filter_type = array_get_path($current_config, "interfaces/{$interface}/outbound_filter_type");
+		$filter_list = array_get_path($current_config, "interfaces/{$interface}/outbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+	}
+	$file_array[] = '';
+
+	// Write the config file
+	if (!file_put_contents(MDNS_BRIDGE_CONF_FILE, implode("\n", $file_array))) {
+		log_error('ERROR: Could not write file ' . MDNS_BRIDGE_CONF_FILE);
+	}
+
+	// Write the rc file
+	$start = MDNS_BRIDGE_CMD . ' -s -c ' . MDNS_BRIDGE_CONF_FILE . ' -p ' . MDNS_BRIDGE_PID_FILE;
+	$stop = '/usr/bin/killall ' . MDNS_BRIDGE_SVC_NAME;
+	write_rcfile(array(
+		"file" => "mdns-bridge.sh",
+		"start" => $start,
+		"stop" => $stop
+		)
+	);
+}
+
+function mdns_bridge_sync_config() {
+	// Stop the service if it is currently running
+	if (is_service_running(MDNS_BRIDGE_SVC_NAME)) {
+		log_error("Stopping service mDNS Bridge");
+		stop_service(MDNS_BRIDGE_SVC_NAME);
+	}
+
+	// If the service is now disabled, remove the config and rc files and return
+	if (!mdns_bridge_enabled()) {
+		unlink_if_exists(MDNS_BRIDGE_RC_FILE);
+		unlink_if_exists(MDNS_BRIDGE_CONF_FILE);
+		return;
+	}
+
+	// Write the config and rc files
+	mdns_bridge_write_files();
+
+	// Take no further action during platform boot
+	if (is_platform_booting()) {
+		return;
+	}
+
+	// Check the CARP VHID if in use
+	$carp_vhid = mdns_bridge_get_carp_vhid();
+	if ($carp_vhid) {
+		$status = get_carp_interface_status($carp_vhid);
+		switch ($status) {
+			case 'MASTER':
+				break;
+
+			case 'BACKUP':
+			case 'INIT':
+				// If the VHID is still initializing, or in backup, don't start the service.
+				log_error("mDNS Bridge service not started due to CARP status ({$status})");
+				return;
+				break;
+
+			default:
+				log_error("mDNS Bridge could not determine CARP status for VHID {$carp_vhid}: status = '{$status}'");
+		}
+
+	}
+
+	// Start the service
+	log_error("Starting service mDNS Bridge");
+	start_service(MDNS_BRIDGE_SVC_NAME);
+}
+
+// Handle CARP notifications
+function mdns_bridge_plugin_carp($pluginparams) {
+	// If mDNS Bridge is not enabled, or a CARP VHID is not in use, return
+	if (!mdns_bridge_enabled()) {
+		return null;
+	}
+	$carp_vhid = mdns_bridge_get_carp_vhid();
+	if (!$carp_vhid) {
+		return null;
+	}
+
+	// Get my VIP
+	$vip = get_configured_vip($carp_vhid);
+	if (!$vip) {
+		log_error("mDNS Bridge cannot find configured VHID ({$carp_vhid}). Check configuration.");
+		return null;
+	}
+
+	// Is the notice for my VIP?
+	$my_id = $vip['vhid'] . '@' . get_real_interface($vip['interface']);
+	if (trim($pluginparams['interface']) != $my_id) {
+		return null;
+	}
+
+	// Start or stop the service as appropriate
+	if ($pluginparams['event'] == "rc.carpmaster") {
+		log_error('Starting service mDNS Bridge on CARP VHID status change');
+		start_service(MDNS_BRIDGE_SVC_NAME);
+	} elseif ($pluginparams['event'] == "rc.carpbackup") {
+		if (is_service_running(MDNS_BRIDGE_SVC_NAME)) {
+			log_error('Stopping service mDNS Bridge on CARP VHID status change');
+			stop_service(MDNS_BRIDGE_SVC_NAME);
+		}
+	}
+}
+
+// Clean up on deinstall
+function mdns_bridge_deinstall_command() {
+	if (is_service_running(MDNS_BRIDGE_SVC_NAME)) {
+		log_error("Stopping service mDNS Bridge");
+		stop_service(MDNS_BRIDGE_SVC_NAME);
+	}
+
+	unlink_if_exists(MDNS_BRIDGE_RC_FILE);
+	unlink_if_exists(MDNS_BRIDGE_CONF_FILE);
+}
diff --git a/net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns_bridge.xml b/net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns_bridge.xml
new file mode 100644
index 000000000000..ecfdc1ffc917
--- /dev/null
+++ b/net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns_bridge.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE packagegui SYSTEM "../schema/packages.dtd">
+<?xml-stylesheet type="text/xsl" href="../xsl/package.xsl"?>
+<packagegui>
+	<copyright>
+	<![CDATA[
+/*
+ * mdns_bridge.xml
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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.
+ */
+	]]>
+	</copyright>
+	<name>mDNS Bridge</name>
+	<title>Services: mDNS Bridge</title>
+	<savetext>Change</savetext>
+	<include_file>/usr/local/pkg/mdns-bridge.inc</include_file>
+	<menu>
+		<name>mDNS Bridge</name>
+		<tooltiptext>mDNS Bridge Settings</tooltiptext>
+		<section>Services</section>
+		<url>/mdns-bridge.php</url>
+	</menu>
+	<service>
+		<name>mdns-bridge</name>
+		<rcfile>mdns-bridge.sh</rcfile>
+		<executable>mdns-bridge</executable>
+		<description>mDNS Bridge</description>
+		<starts_on_sync></starts_on_sync>
+	</service>
+	<plugins>
+		<item>
+			<type>plugin_carp</type>
+		</item>
+	</plugins>
+	<custom_php_resync_config_command>
+		mdns_bridge_sync_config();
+	</custom_php_resync_config_command>
+	<custom_php_pre_deinstall_command>
+		mdns_bridge_deinstall_command();
+	</custom_php_pre_deinstall_command>
+</packagegui>
diff --git a/net/pfSense-pkg-mDNS-Bridge/files/usr/local/share/pfSense-pkg-mDNS-Bridge/info.xml b/net/pfSense-pkg-mDNS-Bridge/files/usr/local/share/pfSense-pkg-mDNS-Bridge/info.xml
new file mode 100644
index 000000000000..882fb9d1065f
--- /dev/null
+++ b/net/pfSense-pkg-mDNS-Bridge/files/usr/local/share/pfSense-pkg-mDNS-Bridge/info.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<pfsensepkgs>
+	<package>
+		<name>mDNS Bridge</name>
+		<internal_name>mDNS-Bridge</internal_name>
+		<website>https://github.com/dennypage/mdns-bridge/</website>
+		<descr><![CDATA[mDNS Bridge provides sharing of Multicast DNS (mDNS) and DNS Service Discovery (DNS-SD) information across network interfaces.]]></descr>
+		<version>%%PKGVERSION%%</version>
+		<configurationfile>mdns_bridge.xml</configurationfile>
+	</package>
+</pfsensepkgs>
diff --git a/net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php b/net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
new file mode 100644
index 000000000000..20adfab1a870
--- /dev/null
+++ b/net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
@@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge';
+$path_enable = 'enable';
+$path_carp_vhid = 'carp_vhid';
+$path_active_interfaces = 'active_interfaces';
+$path_global_ip_protocols = 'global_ip_protocols';
+$path_global_filter_type = 'global_filter_type';
+$path_global_filter_list = 'global_filter_list';
+$path_disable_packet_filtering = 'disable_packet_filtering';
+$path_interfaces = 'interfaces';
+
+// Get the current configuration
+$current_config = config_get_path($package_path, []);
+$pconfig['enable'] = array_get_path($current_config, $path_enable);
+$pconfig['carp_vhid'] = array_get_path($current_config, $path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', array_get_path($current_config, $path_active_interfaces, ''));
+$pconfig['global_ip_protocols'] = array_get_path($current_config, $path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = array_get_path($current_config, $path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = array_get_path($current_config, $path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = array_get_path($current_config, $path_disable_packet_filtering, false);
+$pconfig['interfaces'] = array_get_path($current_config, $path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+// Get the list of available interfaces
+$available_interfaces = get_configured_interface_with_descr();
+foreach ($available_interfaces as $interface => $name) {
+	if (interface_has_gateway($interface) || interface_has_gatewayv6($interface)) {
+		unset($available_interfaces[$interface]);
+	}
+}
+
+if ($_POST) {
+	unset($input_errors);
+	$pconfig = $_POST;
+
+	// Check for Avahi conflict
+	if ($pconfig['enable'] && $avahi_enabled) {
+		$input_errors[] = gettext('Avahi relfection must be disabled before enabling mDNS Bridge');
+	}
+
+	// Validate interfaces
+	if (count($pconfig['active_interfaces']) < 2) {
+		$input_errors[] = gettext('A minimum of two interfaces are required');
+	}
+
+	// Validate and normalize the global filter
+	if ($pconfig['global_filter_type'] != 'none' && trim($pconfig['global_filter_list']) == '') {
+		$pconfig['global_filter_type'] = 'none';
+		$pconfig['global_filter_list'] = '';
+	}
+	if ($pconfig['global_filter_type'] != 'none') {
+		$pconfig['disable_packet_filtering'] = false;
+		$filter_list = array();
+		foreach (explode(',', $pconfig['global_filter_list']) as $filter) {
+			$filter = trim($filter);
+			if (!is_domain($filter, false, false)) {
+				$input_errors[] = sprintf(gettext('Invalid domain in Global Filter List: "%1$s"'), $filter);
+			}
+			$filter_list[] = $filter;
+		}
+		$pconfig['global_filter_list'] = implode(', ', $filter_list);
+	}
+
+	// Validate and normalize the interface filters
+	foreach ($pconfig['active_interfaces'] as $interface) {
+		// Inbound filter
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none' && trim($pconfig['inbound_filter_list_' . $interface]) == '') {
+			$pconfig['inbound_filter_type_' . $interface] = 'none';
+			$pconfig['inbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['inbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Inbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['inbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+
+		// Outbound filter
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none' && trim($pconfig['outbound_filter_list_' . $interface]) == '') {
+			$pconfig['outbound_filter_type_' . $interface] = 'none';
+			$pconfig['outbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['outbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Outbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['outbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+	}
+
+	// Rebuild the interfaces array
+	foreach ($available_interfaces as $interface => $name) {
+		array_set_path($pconfig, "interfaces/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+	}
+
+	// Update the config
+	if (!$input_errors) {
+		// Global settings
+		array_set_path($current_config, $path_enable, $pconfig['enable']);
+		array_set_path($current_config, $path_carp_vhid, $pconfig['carp_vhid']);
+		array_set_path($current_config, $path_active_interfaces, implode(',', $pconfig['active_interfaces']));
+		array_set_path($current_config, $path_global_ip_protocols, $pconfig['global_ip_protocols']);
+		array_set_path($current_config, $path_global_filter_type, $pconfig['global_filter_type']);
+		array_set_path($current_config, $path_global_filter_list, $pconfig['global_filter_list']);
+		array_set_path($current_config, $path_disable_packet_filtering, $pconfig['disable_packet_filtering']);
+
+		// Interface settings
+		foreach ($pconfig['active_interfaces'] as $interface) {
+			array_set_path($current_config, "{$path_interfaces}/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+			array_set_path($current_config, "{$path_interfaces}/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+			array_set_path($current_config, "{$path_interfaces}/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+			array_set_path($current_config, "{$path_interfaces}/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+			array_set_path($current_config, "{$path_interfaces}/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+		}
+
+		// Write the config
+		config_set_path($package_path, $current_config);
+		write_config(gettext("mDNS Bridge settings changed"));
+
+		// Sync the running configuration
+		mdns_bridge_sync_config();
+	}
+}
+
+
+$ip_protocol_types = array(
+	'both' => gettext('IPv4 and IPv6'),
+	'ipv4' => gettext('IPv4 only'),
+	'ipv6' => gettext('IPv6 only') );
+
+$filter_types = array(
+	'none' => gettext('none'),
+	'allow' => gettext('Allow'),
+	'deny' => gettext('Deny') );
+
+$filter_help_text = gettext(
+	'Comma separated list of mDNS names. Most often, a name should ' .
+	'be a single label representing a service name such as ' .
+	'_printer, _ipp, _ipps, _airplay, _hap, _http or _ssh.');
+$filter_placeholder_text = gettext('name1, name2, name3');
+
+
+$pgtitle = array(gettext("Services"), gettext("mDNS Bridge"));
+include("head.inc");
+
+if ($input_errors) {
+	print_input_errors($input_errors);
+}
+
+
+$form = new Form;
+$section = new Form_Section('General Settings');
+
+// Enable
+$section->addInput(new Form_Checkbox(
+	'enable',
+	'Enable',
+	'Enable the mDNS Bridge daemon',
+	$pconfig['enable']
+));
+
+// CARP
+$section->addInput(new Form_Select(
+	'carp_vhid',
+	'CARP Status VHID',
+	$pconfig['carp_vhid'],
+	mdns_bridge_get_carp_list()
+))->setHelp(gettext('Used for HA MASTER/BACKUP status. mDNS Bridge will be started when the chosen VHID is in MASTER status, and stopped when in BACKUP status.'));
+
+// List of interfaces
+$section->addInput(new Form_Select(
+	'active_interfaces',
+	'*Interfaces',
+	$pconfig['active_interfaces'],
+	$available_interfaces,
+	true
+))->addClass('active_interfaces')->setHelp(gettext('Interfaces that the mDNS Bridge daemon will operate on. Two or more interfaces are required.'));
+$form->add($section);
+
+$section = new Form_Section('Global Settings');
+// Global IP protocol list
+$section->addInput(new Form_Select(
+	'global_ip_protocols',
+	'IP Protocols',
+	$pconfig['global_ip_protocols'],
+	$ip_protocol_types
+))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+
+
+// Global filter type
+$group = new Form_Group('Global Filter');
+$group->add(new Form_Select(
+	'global_filter_type',
+	null,
+	$pconfig['global_filter_type'],
+	$filter_types
+))->setHelp(gettext('The global filter is applied to incoming packets on all interfaces prior to any interface specific filters.'));
+$section->add($group);
+
+// Conditional text explaining the currently selected filter type
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_none');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('All mDNS names are allowed by default.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_allow');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_deny');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+
+$group = new Form_Group('Global Filter List');
+$group->addClass('sh_global_filter_list');
+$group->add(new Form_Input(
+	'global_filter_list',
+	null,
+	'text',
+	$pconfig['global_filter_list']
+))->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+$section->add($group);
+
+$form->add($section);
+
+// Interface sections
+foreach ($available_interfaces as $interface => $name) {
+	$interface_config = array_get_path($pconfig, "interfaces/{$interface}", []);
+
+	$section = new Form_Section($name . ' Settings');
+	$section->addClass('sh_interface');
+	$section->addClass('sh_interface_' . $interface);
+
+	// IP protocols
+	$group = new Form_Group('IP Protocols');
+	$group->addClass('sh_interface_protocols');
+	$group->add(new Form_Select(
+		'ip_protocols_' . $interface,
+		null,
+		$interface_config['ip_protocols'],
+		$ip_protocol_types
+	))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+	$section->add($group);
+
+	// Inbound filter type
+	$group = new Form_Group('Inbound Filter');
+	$group->add(new Form_Select(
+		'inbound_filter_type_' . $interface,
+		null,
+		$interface_config['inbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The inbound filter is applied to packets received on the interface following the global filter.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected inbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed inbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	$group = new Form_Group('Inbound Filter List');
+	$group->addClass('sh_interface_inbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'inbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'inbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+
+	// Outbound filter type
+	$group = new Form_Group('Outbound Filter');
+	$group->add(new Form_Select(
+		'outbound_filter_type_' . $interface,
+		null,
+		$interface_config['outbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The outbound filter is applied to packets prior to sending packets on the interface.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected outbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed outbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	// Outbound filter list
+	$group = new Form_Group('Outbound Filter List');
+	$group->addClass('sh_interface_outbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'outbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'outbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+	$form->add($section);
+}
+
+// Advanced option to Disable all packet filtering
+$section = new Form_Section('Advanced');
+$section->addClass('sh_disable_packet_filtering');
+$section->addInput(new Form_Checkbox(
+	'disable_packet_filtering',
+	'Disable Packet Filtering',
+	'Completely disable packet decoding and filtering',
+	$pconfig['disable_packet_filtering']
+))->setHelp(gettext('Selecting this option will cause packets received from one interface to be forwarded directly to neighboring interfaces without any further processing. <b>This option disables all mDNS packet validation. Use with caution.</b>'));
+$form->add($section);
+
+print($form);
+?>
+
+
+<script type="text/javascript">
+//<![CDATA[
+events.push(function() {
+	var available_interfaces = <?=json_encode(array_keys($available_interfaces))?>;
+
+	// Show/hide interface sections
+	function hideInterfaces() {
+		hideClass('sh_interface', true);
+
+		var selected = $(".active_interfaces").val();
+		var length = $(".active_interfaces :selected").length;
+		for (var i = 0; i < length; i++) {
+			hideClass('sh_interface_' + selected[i], false);
+		}
+	}
+
+	// Show/hide interface ip protocols based on global ip protocols
+	function hideInterfaceProtocols() {
+		hideClass('sh_interface_protocols', $('#global_ip_protocols').prop('value') != 'both');
+	}
+
+	// Show/hide advanced
+	function hideAdvanced() {
+		showadvanced = $('#global_filter_type').prop('value') == 'none';
+		if (showadvanced) {
+			var selected = $(".active_interfaces").val();
+			var length = $(".active_interfaces :selected").length;
+			for (var i = 0; i < length; i++) {
+				if ($('#inbound_filter_type_' + selected[i]).prop('value') != 'none' ||
+				    $('#outbound_filter_type_' + selected[i]).prop('value') != 'none') {
+					showadvanced = false;
+					break;
+				}
+			}
+		}
+		hideClass('sh_disable_packet_filtering', !showadvanced);
+	}
+
+	// Show/hide based on global filter
+	function hideGlobalFilter() {
+		hideClass('sh_global_filter_none', $('#global_filter_type').prop('value') != 'none');
+		hideClass('sh_global_filter_allow', $('#global_filter_type').prop('value') != 'allow');
+		hideClass('sh_global_filter_deny', $('#global_filter_type').prop('value') != 'deny');
+		hideClass('sh_global_filter_list', $('#global_filter_type').prop('value') == 'none');
+	}
+
+	// Show/hide based on interface filters
+	function hideInterfaceFilter(interface, direction) {
+		hideClass('sh_interface_' + direction + '_filter_none_' + interface, ($('#' + direction + '_filter_type_' + interface).prop('value') != 'none'));
+		hideClass('sh_interface_' + direction + '_filter_allow_' + interface,($('#' + direction + '_filter_type_' + interface).prop('value') != 'allow'));
+		hideClass('sh_interface_' + direction + '_filter_deny_' + interface, ($('#' + direction + '_filter_type_' + interface).prop('value') != 'deny'));
+		hideClass('sh_interface_' + direction + '_filter_list_' + interface, ($('#' + direction + '_filter_type_' + interface).prop('value') == 'none'));
+	}
+
+	// On changing selection for active interfaces
+	$('.active_interfaces').change(function () {
+		hideInterfaces();
+		hideAdvanced();
+	});
+
+	// On changing selection for global ip protocols
+	$('#global_ip_protocols').change(function() {
+		hideInterfaceProtocols();
+	});
+
+	// On changing selection for global filter
+	$('#global_filter_type').change(function() {
+		hideGlobalFilter();
+		hideAdvanced();
+	});
+
+	// On changing selection for interface filters
+	$("select[id^='inbound_filter_type_']").change(function() {
+		let result;
+		result = $(this).attr('id').match(/^inbound_filter_type_([\w]+)/);
+		if (result && available_interfaces.includes(result[1])) {
+			hideInterfaceFilter(result[1], 'inbound');
+			hideAdvanced();
+		}
+	});
+	$("select[id^='outbound_filter_type_']").change(function() {
+		let result;
+		result = $(this).attr('id').match(/^outbound_filter_type_([\w]+)/);
+		if (result && available_interfaces.includes(result[1])) {
+			hideInterfaceFilter(result[1], 'outbound');
+			hideAdvanced();
+		}
+	});
+
+	// Initial page load
+	hideInterfaces();
+	hideInterfaceProtocols();
+	hideGlobalFilter();
+	hideAdvanced();
+
+	for (let interface of available_interfaces) {
+		hideInterfaceFilter(interface, 'inbound');
+		hideInterfaceFilter(interface, 'outbound');
+	}
+
+});
+//]]>
+</script>
+
+<?php include("foot.inc");
diff --git a/net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/shortcuts/pkg_mdns-bridge.inc b/net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/shortcuts/pkg_mdns-bridge.inc
new file mode 100644
index 000000000000..dc1674ee68e1
--- /dev/null
+++ b/net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/shortcuts/pkg_mdns-bridge.inc
@@ -0,0 +1,26 @@
+<?php
+/*
+ * pkg_mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2022-2024 Rubicon Communications, LLC (Netgate)
+ * 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.
+ */
+
+global $shortcuts;
+
+$shortcuts['mdns-bridge']             = [];
+$shortcuts['mdns-bridge']['main']     = 'mdns-bridge.php';
+$shortcuts['mdns-bridge']['service']  = 'mdns-bridge';
diff --git a/net/pfSense-pkg-mDNS-Bridge/pkg-descr b/net/pfSense-pkg-mDNS-Bridge/pkg-descr
new file mode 100644
index 000000000000..b1aa346b0a84
--- /dev/null
+++ b/net/pfSense-pkg-mDNS-Bridge/pkg-descr
@@ -0,0 +1,4 @@
+mDNS Bridge is a daemon for sharing Multicast DNS (mDNS) information across
+multiple network interfaces. This allows local DNS Service Discovery (DNS-SD)
+across multiple network segments, which is commonly used for sharing network
+print services, audio/video streaming, home automation, etc.
diff --git a/net/pfSense-pkg-mDNS-Bridge/pkg-plist b/net/pfSense-pkg-mDNS-Bridge/pkg-plist
new file mode 100644
index 000000000000..935621524df4
--- /dev/null
+++ b/net/pfSense-pkg-mDNS-Bridge/pkg-plist
@@ -0,0 +1,8 @@
+/etc/inc/priv/mdns-bridge.priv.inc
+pkg/mdns_bridge.xml
+pkg/mdns-bridge.inc
+%%DATADIR%%/info.xml
+www/mdns-bridge.php
+www/shortcuts/pkg_mdns-bridge.inc
+@dir /etc/inc/priv
+@dir /etc/inc


Reviews:
user: login: marcos-ng
body: ### Logging
It'd be best to have dedicated logging for the package. It's fairly straight-forward to do so. You'll need to add the appropriate configuration to info.xml:
https://github.com/pfsense/FreeBSD-ports/blob/4f80f22a9de3e01f744f34ccf8dd5edfb0790947/net/pfSense-pkg-udpbroadcastrelay/files/usr/local/share/pfSense-pkg-udpbroadcastrelay/info.xml#L10

And implement the logging like so:
https://github.com/pfsense/FreeBSD-ports/commit/179fe73ff8fbb18f68d94b6400d06cdac5d25072

### Config calls
Avoid fetching and setting the config multiple times by storing it in a variable first then using the appropriate `array_`* functions. For example:
```
$package_config = config_get_path('installedpackages/mdns-bridge', []);
$ip_protocols = array_get_path($package_config, 'global_ip_protocols', '');
$ip_protocols = array_get_path($package_config, "interfaces/{$interface}/ip_protocols", '');
```
This can help with performance.
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-06T20:23:29Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T04:06:16Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T04:17:49Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T04:18:18Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T04:18:56Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T04:20:06Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T04:22:24Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T04:23:16Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T04:23:51Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T04:26:42Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T05:24:48Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T05:25:09Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T05:28:35Z
user: login: marcos-ng
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T13:55:03Z
user: login: marcos-ng
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T14:07:45Z
user: login: marcos-ng
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T14:11:39Z
user: login: marcos-ng
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T14:13:22Z
user: login: marcos-ng
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T14:23:41Z
user: login: marcos-ng
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T14:26:42Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T18:08:03Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T18:18:39Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T18:34:20Z
user: login: marcos-ng
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T18:38:16Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T18:41:52Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T18:45:34Z
user: login: marcos-ng
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T18:53:19Z
user: login: marcos-ng
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T19:45:43Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T20:47:31Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T21:00:26Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T21:02:28Z
user: login: marcos-ng
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T21:51:23Z
user: login: marcos-ng
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T22:34:03Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T23:02:05Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T23:11:36Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T16:58:03Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T17:19:05Z
user: login: marcos-ng
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T23:08:16Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T17:09:19Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T18:16:20Z
user: login: marcos-ng
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-11T19:01:47Z
user: login: dennypage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T20:55:00Z

Review comments:
diff_hunk: @@ -0,0 +1,46 @@
+
+PORTNAME=	pfSense-pkg-mDNS-Bridge
+PORTVERSION=	1.0
+CATEGORIES=	net
+MASTER_SITES=	# empty
+DISTFILES=	# empty
+
+MAINTAINER=	coreteam@pfsense.org
path: net/pfSense-pkg-mDNS-Bridge/Makefile
user: login: marcos-ng
body: It's best to have your info here if you will be maintaining it.
created_at: 2025-03-06T18:18:53Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,4 @@
+mDNS Bridge is a daemon for sharing Multicast DNS (mDNS) information across
+across multiple network interfaces. This allows local DNS Service Discovery
path: net/pfSense-pkg-mDNS-Bridge/pkg-descr
user: login: marcos-ng
body: `across across`

`segments. Commonly` -> `segments which is commonly`
created_at: 2025-03-06T18:20:07Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,31 @@
+<?php
+/*
+ * mdns-bridge.priv.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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.
+ */
+
+global $priv_list;
+
+$priv_list['page-services-mdns-bridge'] = array();
+$priv_list['page-services-mdns-bridge']['name'] = "WebCfg - Services: mDNS Bridge";
+$priv_list['page-services-mdns-bridge']['descr'] = "Access the mDNS Bridge package gui";
path: net/pfSense-pkg-mDNS-Bridge/files/etc/inc/priv/mdns-bridge.priv.inc
user: login: marcos-ng
body: `gui` -> `GUI`

You may also create the whole array in one go instead of line-by-line.
created_at: 2025-03-06T18:37:44Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<pfsensepkgs>
+	<package>
+		<name>mDNS Bridge</name>
+		<internal_name>mDNS-Bridge</internal_name>
+		<website>https://github.com/dennypage/mdns-bridge/</website>
+		<descr><![CDATA[mDNS Bridge provides sharing of mDNS (Multicast DNS) and DNS-SD (DNS Service Discovery) information across network interfaces.]]></descr>
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/share/pfSense-pkg-mDNS-Bridge/info.xml
user: login: marcos-ng
body: Put the abbreviations for mDNS and DNS-SD in the parenthesis instead.
created_at: 2025-03-06T18:40:10Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: marcos-ng
body: For bool-like config elements, use `config_path_enabled()`. The config element should generally exist when it's enabled and be unset when disabled. For example:
- Enable it: `config_set_path('installedpackages/mdns-bridge/enable', true)`
- Disable it: `config_del_path('installedpackages/mdns-bridge/enable')`
- Check if enabled: `config_path_enabled('installedpackages/mdns-bridge')`
created_at: 2025-03-06T18:58:27Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
+}
+
+// Get the CARP VHID in use by mDNS Bridge
+function mdns_bridge_get_carp_vhid() {
+	$carp_vhid = config_get_path('installedpackages/mdns-bridge/carp_vhid', 'none');
+	if ($carp_vhid != 'none') {
+		return $carp_vhid;
+	}
+	return null;
+}
+
+// Get a CARP VHID list for use in mdns_bridge.php
+function mdns_bridge_get_carp_list() {
+	$list = array('none' => 'none');
+	foreach (get_configured_vip_list('all', VIP_CARP) as $vid => $vaddr) {
+		$vip = get_configured_vip($vid);
+		$list[$vid] = sprintf("VHID %s on %s (%s)", $vip['vhid'], convert_friendly_interface_to_friendly_descr($vip['interface']), $vaddr);
+	}
+	return ($list);
+}
+
+// Write the config and rc files
+function mdns_bridge_write_files() {
+	// Get the real interface names
+	$interfaces = array();
+	foreach (explode(',', config_get_path('installedpackages/mdns-bridge/active_interfaces')) as $interface) {
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: marcos-ng
body: `explode()` expects a string - use an empty string for `config_get_path()`'s `default`.
created_at: 2025-03-06T19:03:17Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
+}
+
+// Get the CARP VHID in use by mDNS Bridge
+function mdns_bridge_get_carp_vhid() {
+	$carp_vhid = config_get_path('installedpackages/mdns-bridge/carp_vhid', 'none');
+	if ($carp_vhid != 'none') {
+		return $carp_vhid;
+	}
+	return null;
+}
+
+// Get a CARP VHID list for use in mdns_bridge.php
+function mdns_bridge_get_carp_list() {
+	$list = array('none' => 'none');
+	foreach (get_configured_vip_list('all', VIP_CARP) as $vid => $vaddr) {
+		$vip = get_configured_vip($vid);
+		$list[$vid] = sprintf("VHID %s on %s (%s)", $vip['vhid'], convert_friendly_interface_to_friendly_descr($vip['interface']), $vaddr);
+	}
+	return ($list);
+}
+
+// Write the config and rc files
+function mdns_bridge_write_files() {
+	// Get the real interface names
+	$interfaces = array();
+	foreach (explode(',', config_get_path('installedpackages/mdns-bridge/active_interfaces')) as $interface) {
+		$interfaces[$interface] = get_real_interface($interface);
+	}
+
+	// Global section
+	$file_array = array();
+	$file_array[] = '# This file is generated by the pfSense mDNS Bridge package.';
+	$file_array[] = '# Do not edit this file, it will be overwritten automatically.';
+
+	$file_array[] = '[global]';
+	$file_array[] = 'interfaces = ' . implode(', ', $interfaces);
+
+	$ip_protocols = config_get_path('installedpackages/mdns-bridge/global_ip_protocols');
+	switch ($ip_protocols) {
+		case 'ipv4':
+			$file_array['global_ip_protocols'] = 'disable-ipv6 = yes';
+			break;
+		case 'ipv6':
+			$file_array['global_ip_protocols'] = 'disable-ipv4 = yes';
+			break;
+	}
+
+	$filter_type = config_get_path('installedpackages/mdns-bridge/global_filter_type');
+	$filter_list = config_get_path('installedpackages/mdns-bridge/global_filter_list');
+	switch ($filter_type) {
+		case 'allow':
+			$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+			break;
+		case 'deny':
+			$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+			break;
+		default:
+			$disable_packet_filtering = config_get_path('installedpackages/mdns-bridge/disable_packet_filtering');
+			if ($disable_packet_filtering)
+			{
+				$file_array['disable-packet-filtering'] = 'disable-packet-filtering = yes';
+			}
+	}
+
+
+	// Interface sections
+	foreach ($interfaces as $interface => $real_interface) {
+		$file_array[] = '';
+		$file_array[] = "[{$real_interface}]";
+
+		if (!$file_array['global_ip_protocols']) {
+			$ip_protocols = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/ip_protocols");
+			switch ($ip_protocols) {
+				case 'ipv4':
+					$file_array[] = 'disable-ipv6 = yes';
+					break;
+				case 'ipv6':
+					$file_array[] = 'disable-ipv4 = yes';
+					break;
+			}
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+	}
+	$file_array[] = '';
+
+	// Write the config file
+	if (!file_put_contents(MDNS_BRIDGE_CONF_FILE, implode("\n", $file_array))) {
+		log_error('ERROR: Could not write file ' . MDNS_BRIDGE_CONF_FILE);
+	}
+
+	// Write the rc file
+	$start = MDNS_BRIDGE_CMD . ' -s -c ' . MDNS_BRIDGE_CONF_FILE . ' -p ' . MDNS_BRIDGE_PID_FILE;
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: marcos-ng
body: Avoid single-use variables. These can be set directly in the array.
created_at: 2025-03-06T19:19:48Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
+}
+
+// Get the CARP VHID in use by mDNS Bridge
+function mdns_bridge_get_carp_vhid() {
+	$carp_vhid = config_get_path('installedpackages/mdns-bridge/carp_vhid', 'none');
+	if ($carp_vhid != 'none') {
+		return $carp_vhid;
+	}
+	return null;
+}
+
+// Get a CARP VHID list for use in mdns_bridge.php
+function mdns_bridge_get_carp_list() {
+	$list = array('none' => 'none');
+	foreach (get_configured_vip_list('all', VIP_CARP) as $vid => $vaddr) {
+		$vip = get_configured_vip($vid);
+		$list[$vid] = sprintf("VHID %s on %s (%s)", $vip['vhid'], convert_friendly_interface_to_friendly_descr($vip['interface']), $vaddr);
+	}
+	return ($list);
+}
+
+// Write the config and rc files
+function mdns_bridge_write_files() {
+	// Get the real interface names
+	$interfaces = array();
+	foreach (explode(',', config_get_path('installedpackages/mdns-bridge/active_interfaces')) as $interface) {
+		$interfaces[$interface] = get_real_interface($interface);
+	}
+
+	// Global section
+	$file_array = array();
+	$file_array[] = '# This file is generated by the pfSense mDNS Bridge package.';
+	$file_array[] = '# Do not edit this file, it will be overwritten automatically.';
+
+	$file_array[] = '[global]';
+	$file_array[] = 'interfaces = ' . implode(', ', $interfaces);
+
+	$ip_protocols = config_get_path('installedpackages/mdns-bridge/global_ip_protocols');
+	switch ($ip_protocols) {
+		case 'ipv4':
+			$file_array['global_ip_protocols'] = 'disable-ipv6 = yes';
+			break;
+		case 'ipv6':
+			$file_array['global_ip_protocols'] = 'disable-ipv4 = yes';
+			break;
+	}
+
+	$filter_type = config_get_path('installedpackages/mdns-bridge/global_filter_type');
+	$filter_list = config_get_path('installedpackages/mdns-bridge/global_filter_list');
+	switch ($filter_type) {
+		case 'allow':
+			$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+			break;
+		case 'deny':
+			$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+			break;
+		default:
+			$disable_packet_filtering = config_get_path('installedpackages/mdns-bridge/disable_packet_filtering');
+			if ($disable_packet_filtering)
+			{
+				$file_array['disable-packet-filtering'] = 'disable-packet-filtering = yes';
+			}
+	}
+
+
+	// Interface sections
+	foreach ($interfaces as $interface => $real_interface) {
+		$file_array[] = '';
+		$file_array[] = "[{$real_interface}]";
+
+		if (!$file_array['global_ip_protocols']) {
+			$ip_protocols = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/ip_protocols");
+			switch ($ip_protocols) {
+				case 'ipv4':
+					$file_array[] = 'disable-ipv6 = yes';
+					break;
+				case 'ipv6':
+					$file_array[] = 'disable-ipv4 = yes';
+					break;
+			}
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+	}
+	$file_array[] = '';
+
+	// Write the config file
+	if (!file_put_contents(MDNS_BRIDGE_CONF_FILE, implode("\n", $file_array))) {
+		log_error('ERROR: Could not write file ' . MDNS_BRIDGE_CONF_FILE);
+	}
+
+	// Write the rc file
+	$start = MDNS_BRIDGE_CMD . ' -s -c ' . MDNS_BRIDGE_CONF_FILE . ' -p ' . MDNS_BRIDGE_PID_FILE;
+	$stop = '/usr/bin/killall ' . MDNS_BRIDGE_SVC_NAME;
+	write_rcfile(array(
+		"file" => "mdns-bridge.sh",
+		"start" => $start,
+		"stop" => $stop
+		)
+	);
+}
+
+function mdns_bridge_sync_config() {
+	// Stop the service if it is currently running
+	if (is_service_running(MDNS_BRIDGE_SVC_NAME)) {
+		log_error("Stopping service mDNS Bridge");
+		stop_service(MDNS_BRIDGE_SVC_NAME);
+	}
+
+	// If the service is now disabled, remove the config and rc files and return
+	if (!mdns_bridge_enabled()) {
+		unlink_if_exists(MDNS_BRIDGE_RC_FILE);
+		unlink_if_exists(MDNS_BRIDGE_CONF_FILE);
+		return;
+	}
+	
+	// Write the config and rc files
+	mdns_bridge_write_files();
+
+	// Take no further action during platform boot
+	if (platform_booting()) {
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: marcos-ng
body: Use `is_platform_booting()` instead.
created_at: 2025-03-06T19:22:25Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
+}
+
+// Get the CARP VHID in use by mDNS Bridge
+function mdns_bridge_get_carp_vhid() {
+	$carp_vhid = config_get_path('installedpackages/mdns-bridge/carp_vhid', 'none');
+	if ($carp_vhid != 'none') {
+		return $carp_vhid;
+	}
+	return null;
+}
+
+// Get a CARP VHID list for use in mdns_bridge.php
+function mdns_bridge_get_carp_list() {
+	$list = array('none' => 'none');
+	foreach (get_configured_vip_list('all', VIP_CARP) as $vid => $vaddr) {
+		$vip = get_configured_vip($vid);
+		$list[$vid] = sprintf("VHID %s on %s (%s)", $vip['vhid'], convert_friendly_interface_to_friendly_descr($vip['interface']), $vaddr);
+	}
+	return ($list);
+}
+
+// Write the config and rc files
+function mdns_bridge_write_files() {
+	// Get the real interface names
+	$interfaces = array();
+	foreach (explode(',', config_get_path('installedpackages/mdns-bridge/active_interfaces')) as $interface) {
+		$interfaces[$interface] = get_real_interface($interface);
+	}
+
+	// Global section
+	$file_array = array();
+	$file_array[] = '# This file is generated by the pfSense mDNS Bridge package.';
+	$file_array[] = '# Do not edit this file, it will be overwritten automatically.';
+
+	$file_array[] = '[global]';
+	$file_array[] = 'interfaces = ' . implode(', ', $interfaces);
+
+	$ip_protocols = config_get_path('installedpackages/mdns-bridge/global_ip_protocols');
+	switch ($ip_protocols) {
+		case 'ipv4':
+			$file_array['global_ip_protocols'] = 'disable-ipv6 = yes';
+			break;
+		case 'ipv6':
+			$file_array['global_ip_protocols'] = 'disable-ipv4 = yes';
+			break;
+	}
+
+	$filter_type = config_get_path('installedpackages/mdns-bridge/global_filter_type');
+	$filter_list = config_get_path('installedpackages/mdns-bridge/global_filter_list');
+	switch ($filter_type) {
+		case 'allow':
+			$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+			break;
+		case 'deny':
+			$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+			break;
+		default:
+			$disable_packet_filtering = config_get_path('installedpackages/mdns-bridge/disable_packet_filtering');
+			if ($disable_packet_filtering)
+			{
+				$file_array['disable-packet-filtering'] = 'disable-packet-filtering = yes';
+			}
+	}
+
+
+	// Interface sections
+	foreach ($interfaces as $interface => $real_interface) {
+		$file_array[] = '';
+		$file_array[] = "[{$real_interface}]";
+
+		if (!$file_array['global_ip_protocols']) {
+			$ip_protocols = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/ip_protocols");
+			switch ($ip_protocols) {
+				case 'ipv4':
+					$file_array[] = 'disable-ipv6 = yes';
+					break;
+				case 'ipv6':
+					$file_array[] = 'disable-ipv4 = yes';
+					break;
+			}
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+	}
+	$file_array[] = '';
+
+	// Write the config file
+	if (!file_put_contents(MDNS_BRIDGE_CONF_FILE, implode("\n", $file_array))) {
+		log_error('ERROR: Could not write file ' . MDNS_BRIDGE_CONF_FILE);
+	}
+
+	// Write the rc file
+	$start = MDNS_BRIDGE_CMD . ' -s -c ' . MDNS_BRIDGE_CONF_FILE . ' -p ' . MDNS_BRIDGE_PID_FILE;
+	$stop = '/usr/bin/killall ' . MDNS_BRIDGE_SVC_NAME;
+	write_rcfile(array(
+		"file" => "mdns-bridge.sh",
+		"start" => $start,
+		"stop" => $stop
+		)
+	);
+}
+
+function mdns_bridge_sync_config() {
+	// Stop the service if it is currently running
+	if (is_service_running(MDNS_BRIDGE_SVC_NAME)) {
+		log_error("Stopping service mDNS Bridge");
+		stop_service(MDNS_BRIDGE_SVC_NAME);
+	}
+
+	// If the service is now disabled, remove the config and rc files and return
+	if (!mdns_bridge_enabled()) {
+		unlink_if_exists(MDNS_BRIDGE_RC_FILE);
+		unlink_if_exists(MDNS_BRIDGE_CONF_FILE);
+		return;
+	}
+	
+	// Write the config and rc files
+	mdns_bridge_write_files();
+
+	// Take no further action during platform boot
+	if (platform_booting()) {
+		return;
+	}
+
+	// Check the CARP VHID if in use
+	$carp_vhid = mdns_bridge_get_carp_vhid();
+	if ($carp_vhid) {
+		$status = get_carp_interface_status($carp_vhid);
+		switch ($status) {
+			case 'MASTER':
+				break;
+
+			case 'BACKUP':
+			case 'INIT':
+				// If the VHID is still initializing, or in backup, don't start the service.
+				log_error("mDNS Bridge service not started due to CARP status ({$status})");
+				return;
+				break;
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: marcos-ng
body: This `break;` is unreachable/redundant.
created_at: 2025-03-06T19:23:57Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
+}
+
+// Get the CARP VHID in use by mDNS Bridge
+function mdns_bridge_get_carp_vhid() {
+	$carp_vhid = config_get_path('installedpackages/mdns-bridge/carp_vhid', 'none');
+	if ($carp_vhid != 'none') {
+		return $carp_vhid;
+	}
+	return null;
+}
+
+// Get a CARP VHID list for use in mdns_bridge.php
+function mdns_bridge_get_carp_list() {
+	$list = array('none' => 'none');
+	foreach (get_configured_vip_list('all', VIP_CARP) as $vid => $vaddr) {
+		$vip = get_configured_vip($vid);
+		$list[$vid] = sprintf("VHID %s on %s (%s)", $vip['vhid'], convert_friendly_interface_to_friendly_descr($vip['interface']), $vaddr);
+	}
+	return ($list);
+}
+
+// Write the config and rc files
+function mdns_bridge_write_files() {
+	// Get the real interface names
+	$interfaces = array();
+	foreach (explode(',', config_get_path('installedpackages/mdns-bridge/active_interfaces')) as $interface) {
+		$interfaces[$interface] = get_real_interface($interface);
+	}
+
+	// Global section
+	$file_array = array();
+	$file_array[] = '# This file is generated by the pfSense mDNS Bridge package.';
+	$file_array[] = '# Do not edit this file, it will be overwritten automatically.';
+
+	$file_array[] = '[global]';
+	$file_array[] = 'interfaces = ' . implode(', ', $interfaces);
+
+	$ip_protocols = config_get_path('installedpackages/mdns-bridge/global_ip_protocols');
+	switch ($ip_protocols) {
+		case 'ipv4':
+			$file_array['global_ip_protocols'] = 'disable-ipv6 = yes';
+			break;
+		case 'ipv6':
+			$file_array['global_ip_protocols'] = 'disable-ipv4 = yes';
+			break;
+	}
+
+	$filter_type = config_get_path('installedpackages/mdns-bridge/global_filter_type');
+	$filter_list = config_get_path('installedpackages/mdns-bridge/global_filter_list');
+	switch ($filter_type) {
+		case 'allow':
+			$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+			break;
+		case 'deny':
+			$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+			break;
+		default:
+			$disable_packet_filtering = config_get_path('installedpackages/mdns-bridge/disable_packet_filtering');
+			if ($disable_packet_filtering)
+			{
+				$file_array['disable-packet-filtering'] = 'disable-packet-filtering = yes';
+			}
+	}
+
+
+	// Interface sections
+	foreach ($interfaces as $interface => $real_interface) {
+		$file_array[] = '';
+		$file_array[] = "[{$real_interface}]";
+
+		if (!$file_array['global_ip_protocols']) {
+			$ip_protocols = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/ip_protocols");
+			switch ($ip_protocols) {
+				case 'ipv4':
+					$file_array[] = 'disable-ipv6 = yes';
+					break;
+				case 'ipv6':
+					$file_array[] = 'disable-ipv4 = yes';
+					break;
+			}
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+	}
+	$file_array[] = '';
+
+	// Write the config file
+	if (!file_put_contents(MDNS_BRIDGE_CONF_FILE, implode("\n", $file_array))) {
+		log_error('ERROR: Could not write file ' . MDNS_BRIDGE_CONF_FILE);
+	}
+
+	// Write the rc file
+	$start = MDNS_BRIDGE_CMD . ' -s -c ' . MDNS_BRIDGE_CONF_FILE . ' -p ' . MDNS_BRIDGE_PID_FILE;
+	$stop = '/usr/bin/killall ' . MDNS_BRIDGE_SVC_NAME;
+	write_rcfile(array(
+		"file" => "mdns-bridge.sh",
+		"start" => $start,
+		"stop" => $stop
+		)
+	);
+}
+
+function mdns_bridge_sync_config() {
+	// Stop the service if it is currently running
+	if (is_service_running(MDNS_BRIDGE_SVC_NAME)) {
+		log_error("Stopping service mDNS Bridge");
+		stop_service(MDNS_BRIDGE_SVC_NAME);
+	}
+
+	// If the service is now disabled, remove the config and rc files and return
+	if (!mdns_bridge_enabled()) {
+		unlink_if_exists(MDNS_BRIDGE_RC_FILE);
+		unlink_if_exists(MDNS_BRIDGE_CONF_FILE);
+		return;
+	}
+	
+	// Write the config and rc files
+	mdns_bridge_write_files();
+
+	// Take no further action during platform boot
+	if (platform_booting()) {
+		return;
+	}
+
+	// Check the CARP VHID if in use
+	$carp_vhid = mdns_bridge_get_carp_vhid();
+	if ($carp_vhid) {
+		$status = get_carp_interface_status($carp_vhid);
+		switch ($status) {
+			case 'MASTER':
+				break;
+
+			case 'BACKUP':
+			case 'INIT':
+				// If the VHID is still initializing, or in backup, don't start the service.
+				log_error("mDNS Bridge service not started due to CARP status ({$status})");
+				return;
+				break;
+
+			default:
+				log_error("mDNS Bridge could not determine CARP status for VHID {$carp_vhid}: status = '{$status}'");
+		}
+
+	}
+
+	// Start the service
+	log_error("Starting service mDNS Bridge");
+	start_service(MDNS_BRIDGE_SVC_NAME);
+}
+
+// Handle CARP notifications
+function mdns_bridge_plugin_carp($pluginparams) {
+	global $config;
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: marcos-ng
body: Remove this global - it should never be used in favor of config_* functions.
created_at: 2025-03-06T19:26:29Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: marcos-ng
body: Don't assume `lan` is any particular kind of interface. Simply default to no selection.
created_at: 2025-03-06T19:43:52Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: marcos-ng
body: Don't assume `wan` is any particular kind of interface. If there is something in particular that disqualifies an interface from being available, check for that specifically. See here for an example:
https://github.com/pfsense/FreeBSD-ports/blob/4f80f22a9de3e01f744f34ccf8dd5edfb0790947/net/pfSense-pkg-udpbroadcastrelay/files/usr/local/pkg/udpbroadcastrelay/udpbroadcastrelay.inc#L321
created_at: 2025-03-06T20:12:54Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
+
+if ($_POST) {
+	unset($input_errors);
+	$pconfig = $_POST;
+
+	// Check for Avahi conflict
+	if ($pconfig['enable'] && $avahi_enabled) {
+		$input_errors[] = gettext('Avahi relfection must be disabled before enabling mDNS Bridge');
+	}
+
+	// Validate interfaces
+	if (count($pconfig['active_interfaces']) < 2) {
+		$input_errors[] = gettext('A minimum of two interfaces are required');
+	}
+
+	// Vaidate and normalize the global filter
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: marcos-ng
body: Typo `Vaidate`
created_at: 2025-03-06T20:13:11Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
+
+if ($_POST) {
+	unset($input_errors);
+	$pconfig = $_POST;
+
+	// Check for Avahi conflict
+	if ($pconfig['enable'] && $avahi_enabled) {
+		$input_errors[] = gettext('Avahi relfection must be disabled before enabling mDNS Bridge');
+	}
+
+	// Validate interfaces
+	if (count($pconfig['active_interfaces']) < 2) {
+		$input_errors[] = gettext('A minimum of two interfaces are required');
+	}
+
+	// Vaidate and normalize the global filter
+	if ($pconfig['global_filter_type'] != 'none' && trim($pconfig['global_filter_list']) == '') {
+		$pconfig['global_filter_type'] = 'none';
+		$pconfig['global_filter_list'] = '';
+	}
+	if ($pconfig['global_filter_type'] != 'none') {
+		$pconfig['disable_packet_filtering'] = false;
+		$filter_list = array();
+		foreach (explode(',', $pconfig['global_filter_list']) as $filter) {
+			$filter = trim($filter);
+			if (!is_domain($filter, false, false)) {
+				$input_errors[] = sprintf(gettext('Invalid domain in Global Filter List: "%1$s"'), $filter);
+			}
+			$filter_list[] = $filter;
+		}
+		$pconfig['global_filter_list'] = implode(', ', $filter_list);
+	}
+
+	// Validate and normalize the interface filters
+	foreach ($pconfig['active_interfaces'] as $interface) {
+		// Inbound filter
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none' && trim($pconfig['inbound_filter_list_' . $interface]) == '') {
+			$pconfig['inbound_filter_type_' . $interface] = 'none';
+			$pconfig['inbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['inbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Inbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['inbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+
+		// Outbound filter
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none' && trim($pconfig['outbound_filter_list_' . $interface]) == '') {
+			$pconfig['outbound_filter_type_' . $interface] = 'none';
+			$pconfig['outbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['outbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Outbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['outbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+	}
+
+	// Rebuild the interfaces array
+	foreach ($available_interfaces as $interface => $name) {
+		array_set_path($pconfig, "interfaces/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+	}
+
+	// Update the config
+	if (!$input_errors) {
+		// Global settings
+		config_set_path($path_enable, $pconfig['enable']);
+		config_set_path($path_carp_vhid, $pconfig['carp_vhid']);
+		config_set_path($path_active_interfaces, implode(',', $pconfig['active_interfaces']));
+		config_set_path($path_global_ip_protocols, $pconfig['global_ip_protocols']);
+		config_set_path($path_global_filter_type, $pconfig['global_filter_type']);
+		config_set_path($path_global_filter_list, $pconfig['global_filter_list']);
+		config_set_path($path_disable_packet_filtering, $pconfig['disable_packet_filtering']);
+
+		// Interface settings
+		foreach ($pconfig['active_interfaces'] as $interface) {
+			config_set_path("{$path_interfaces}/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+		}
+
+		// Write the config
+		write_config(gettext("mDNS Bridge settings changed"));
+
+		// Sync the running configuration
+		mdns_bridge_sync_config();
+	}
+}
+
+
+$ip_protocol_types = array(
+	'both' => gettext('IPv4 and IPv6'),
+	'ipv4' => gettext('IPv4 only'),
+	'ipv6' => gettext('IPv6 only') );
+
+$filter_types = array(
+	'none' => gettext('none'),
+	'allow' => gettext('Allow'),
+	'deny' => gettext('Deny') );
+
+$filter_help_text = gettext(
+	'Comma separated list of mDNS names. Most often, a name should ' .
+	'be a single label representing a service name such as ' .
+	'_printer, _ipp, _ipps, _airplay, _hap, _http or _ssh.');
+$filter_placeholder_text = gettext('name1, name2, name3');
+
+
+$pgtitle = array(gettext("Services"), gettext("mDNS Bridge"));
+include("head.inc");
+
+if ($input_errors) {
+	print_input_errors($input_errors);
+}
+
+
+$form = new Form;
+$section = new Form_Section('General Settings');
+
+// Enable
+$section->addInput(new Form_Checkbox(
+	'enable',
+	'Enable',
+	'Enable the mDNS Bridge daemon',
+	$pconfig['enable']
+));
+
+// CARP
+$section->addInput(new Form_Select(
+	'carp_vhid',
+	'CARP Status VHID',
+	$pconfig['carp_vhid'],
+	mdns_bridge_get_carp_list()
+))->setHelp(gettext('Used for HA MASTER/BACKUP status. mDNS Bridge will be started when the chosen VHID is in MASTER status, and stopped when in BACKUP status.'));
+
+// List of interfaces
+$section->addInput(new Form_Select(
+	'active_interfaces',
+	'*Interfaces',
+	$pconfig['active_interfaces'],
+	$available_interfaces,
+	true
+))->addClass('active_interfaces')->setHelp(gettext('Interfaces that the mDNS Bridge daemon will operate on. Two or more interfaces are required.'));
+$form->add($section);
+
+$section = new Form_Section('Global Settings');
+// Global IP protocol list
+$section->addInput(new Form_Select(
+	'global_ip_protocols',
+	'IP Protocols',
+	$pconfig['global_ip_protocols'],
+	$ip_protocol_types
+))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+
+
+// Global filter type
+$group = new Form_Group('Global Filter');
+$group->add(new Form_Select(
+	'global_filter_type',
+	null,
+	$pconfig['global_filter_type'],
+	$filter_types
+))->setHelp(gettext('The global filter is applied to incoming packets on all interfaces prior to any interface specific filters.'));
+$section->add($group);
+
+// Conditional text explaining the currently selected filter type
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_none');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('All mDNS names are allowed by default.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_allow');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_deny');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+
+$group = new Form_Group('Global Filter List');
+$group->addClass('sh_global_filter_list');
+$group->add(new Form_Input(
+	'global_filter_list',
+	null,
+	'text',
+	$pconfig['global_filter_list']
+))->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+$section->add($group);
+
+$form->add($section);
+
+// Interface sections
+foreach ($available_interfaces as $interface => $name) {
+	$interface_config = array_get_path($pconfig, "interfaces/{$interface}", []);
+
+	$section = new Form_Section($name . ' Settings');
+	$section->addClass('sh_interface');
+	$section->addClass('sh_interface_' . $interface);
+
+	// IP protocols
+	$group = new Form_Group('IP Protocols');
+	$group->addClass('sh_interface_protocols');
+	$group->add(new Form_Select(
+		'ip_protocols_' . $interface,
+		null,
+		$interface_config['ip_protocols'],
+		$ip_protocol_types
+	))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+	$section->add($group);
+
+	// Inbound filter type
+	$group = new Form_Group('Inbound Filter');
+	$group->add(new Form_Select(
+		'inbound_filter_type_' . $interface,
+		null,
+		$interface_config['inbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The inbound filter is applied to packets received on the interface following the global filter.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected inbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed inbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	$group = new Form_Group('Inbound Filter List');
+	$group->addClass('sh_interface_inbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'inbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'inbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+
+	// Outbound filter type
+	$group = new Form_Group('Outbound Filter');
+	$group->add(new Form_Select(
+		'outbound_filter_type_' . $interface,
+		null,
+		$interface_config['outbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The outbound filter is applied to packets prior to sending packets on the interface.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected outbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed outbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	// Outbound filter list
+	$group = new Form_Group('Outbound Filter List');
+	$group->addClass('sh_interface_outbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'outbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'outbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+	$form->add($section);
+}
+
+// Advanced option to Disable all packet filtering
+$section = new Form_Section('Advanced');
+$section->addClass('sh_disable_packet_filtering');
+$section->addInput(new Form_Checkbox(
+	'disable_packet_filtering',
+	'Disable Packet Filtering',
+	'Completely disable packet decoding and filtering',
+	$pconfig['disable_packet_filtering']
+))->setHelp(gettext('Selecting this option will cause packets received from one interface to be forwarded directly to neighboring interfaces without any further processing. <b>This option disables all mDNS packet validation. Use with caution.</b>'));
+$form->add($section);
+
+print($form);
+?>
+
+
+<script type="text/javascript">
+//<![CDATA[
+events.push(function() {
+
+	// Show/hide interface sections
+	function hideInterfaces() {
+		hideClass('sh_interface', true);
+
+		var selected = $(".active_interfaces").val();
+		var length = $(".active_interfaces :selected").length;
+
+		for (var i=0; i<length; i++) {
+			hideClass('sh_interface_' + selected[i], false);
+		}
+	}
+
+	// Show/hide interface ip protocols based on global ip protocols
+	function hideInterfaceProtocols() {
+		hideClass('sh_interface_protocols', $('#global_ip_protocols').prop('value') != 'both');
+	}
+
+	// Show/hide the advanced
+	function hideAdvanced() {
+<?php
+		// This currently looks at all available interfaces. It might be preferable to only look at selected interfaces.
+		echo "showadvanced = ($('#global_filter_type').prop('value') == 'none')\n";
+		foreach ($available_interfaces as $interface => $name) {
+			echo " && $('#inbound_filter_type_{$interface}').prop('value') == 'none'\n";
+			echo " && $('#outbound_filter_type_{$interface}').prop('value') == 'none'\n";
+		}
+		echo ";\n";
+?>
+		hideClass('sh_disable_packet_filtering', !showadvanced);
+	}
+
+	// Show/hide based on global filter
+	function hideGlobalFilter() {
+		hideClass('sh_global_filter_none', $('#global_filter_type').prop('value') != 'none');
+		hideClass('sh_global_filter_allow', $('#global_filter_type').prop('value') != 'allow');
+		hideClass('sh_global_filter_deny', $('#global_filter_type').prop('value') != 'deny');
+		hideClass('sh_global_filter_list', $('#global_filter_type').prop('value') == 'none');
+	}
+
+<?php
+	// Show/hide based on interface filters
+	foreach ($available_interfaces as $interface => $name) {
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: marcos-ng
body: I think a better approach here is to store `$available_interfaces` in a JS variable, loop through it, and call the relevant functions with the interface values as a function parameter.
created_at: 2025-03-06T20:20:56Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,46 @@
+
+PORTNAME=	pfSense-pkg-mDNS-Bridge
+PORTVERSION=	1.0
+CATEGORIES=	net
+MASTER_SITES=	# empty
+DISTFILES=	# empty
+
+MAINTAINER=	coreteam@pfsense.org
path: net/pfSense-pkg-mDNS-Bridge/Makefile
user: login: dennypage
body: I'm okay with that.
created_at: 2025-03-07T04:06:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,31 @@
+<?php
+/*
+ * mdns-bridge.priv.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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.
+ */
+
+global $priv_list;
+
+$priv_list['page-services-mdns-bridge'] = array();
+$priv_list['page-services-mdns-bridge']['name'] = "WebCfg - Services: mDNS Bridge";
+$priv_list['page-services-mdns-bridge']['descr'] = "Access the mDNS Bridge package gui";
path: net/pfSense-pkg-mDNS-Bridge/files/etc/inc/priv/mdns-bridge.priv.inc
user: login: dennypage
body: I'm okay changing gui -> GUI.

I understand that entire array could be created at once, but the element by element approach is used by priv.inc content from the base pfSense system (/etc/inc/priv.defs.inc), as well as other packages, so to me it seems best to stay within that custom.
created_at: 2025-03-07T04:17:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<pfsensepkgs>
+	<package>
+		<name>mDNS Bridge</name>
+		<internal_name>mDNS-Bridge</internal_name>
+		<website>https://github.com/dennypage/mdns-bridge/</website>
+		<descr><![CDATA[mDNS Bridge provides sharing of mDNS (Multicast DNS) and DNS-SD (DNS Service Discovery) information across network interfaces.]]></descr>
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/share/pfSense-pkg-mDNS-Bridge/info.xml
user: login: dennypage
body: I'm fine doing this.
created_at: 2025-03-07T04:18:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: dennypage
body: I'll have a look at this.
created_at: 2025-03-07T04:18:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
+}
+
+// Get the CARP VHID in use by mDNS Bridge
+function mdns_bridge_get_carp_vhid() {
+	$carp_vhid = config_get_path('installedpackages/mdns-bridge/carp_vhid', 'none');
+	if ($carp_vhid != 'none') {
+		return $carp_vhid;
+	}
+	return null;
+}
+
+// Get a CARP VHID list for use in mdns_bridge.php
+function mdns_bridge_get_carp_list() {
+	$list = array('none' => 'none');
+	foreach (get_configured_vip_list('all', VIP_CARP) as $vid => $vaddr) {
+		$vip = get_configured_vip($vid);
+		$list[$vid] = sprintf("VHID %s on %s (%s)", $vip['vhid'], convert_friendly_interface_to_friendly_descr($vip['interface']), $vaddr);
+	}
+	return ($list);
+}
+
+// Write the config and rc files
+function mdns_bridge_write_files() {
+	// Get the real interface names
+	$interfaces = array();
+	foreach (explode(',', config_get_path('installedpackages/mdns-bridge/active_interfaces')) as $interface) {
+		$interfaces[$interface] = get_real_interface($interface);
+	}
+
+	// Global section
+	$file_array = array();
+	$file_array[] = '# This file is generated by the pfSense mDNS Bridge package.';
+	$file_array[] = '# Do not edit this file, it will be overwritten automatically.';
+
+	$file_array[] = '[global]';
+	$file_array[] = 'interfaces = ' . implode(', ', $interfaces);
+
+	$ip_protocols = config_get_path('installedpackages/mdns-bridge/global_ip_protocols');
+	switch ($ip_protocols) {
+		case 'ipv4':
+			$file_array['global_ip_protocols'] = 'disable-ipv6 = yes';
+			break;
+		case 'ipv6':
+			$file_array['global_ip_protocols'] = 'disable-ipv4 = yes';
+			break;
+	}
+
+	$filter_type = config_get_path('installedpackages/mdns-bridge/global_filter_type');
+	$filter_list = config_get_path('installedpackages/mdns-bridge/global_filter_list');
+	switch ($filter_type) {
+		case 'allow':
+			$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+			break;
+		case 'deny':
+			$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+			break;
+		default:
+			$disable_packet_filtering = config_get_path('installedpackages/mdns-bridge/disable_packet_filtering');
+			if ($disable_packet_filtering)
+			{
+				$file_array['disable-packet-filtering'] = 'disable-packet-filtering = yes';
+			}
+	}
+
+
+	// Interface sections
+	foreach ($interfaces as $interface => $real_interface) {
+		$file_array[] = '';
+		$file_array[] = "[{$real_interface}]";
+
+		if (!$file_array['global_ip_protocols']) {
+			$ip_protocols = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/ip_protocols");
+			switch ($ip_protocols) {
+				case 'ipv4':
+					$file_array[] = 'disable-ipv6 = yes';
+					break;
+				case 'ipv6':
+					$file_array[] = 'disable-ipv4 = yes';
+					break;
+			}
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+	}
+	$file_array[] = '';
+
+	// Write the config file
+	if (!file_put_contents(MDNS_BRIDGE_CONF_FILE, implode("\n", $file_array))) {
+		log_error('ERROR: Could not write file ' . MDNS_BRIDGE_CONF_FILE);
+	}
+
+	// Write the rc file
+	$start = MDNS_BRIDGE_CMD . ' -s -c ' . MDNS_BRIDGE_CONF_FILE . ' -p ' . MDNS_BRIDGE_PID_FILE;
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: dennypage
body: I find explicit setting of the variable to be more readable for maintenance.
created_at: 2025-03-07T04:20:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
+}
+
+// Get the CARP VHID in use by mDNS Bridge
+function mdns_bridge_get_carp_vhid() {
+	$carp_vhid = config_get_path('installedpackages/mdns-bridge/carp_vhid', 'none');
+	if ($carp_vhid != 'none') {
+		return $carp_vhid;
+	}
+	return null;
+}
+
+// Get a CARP VHID list for use in mdns_bridge.php
+function mdns_bridge_get_carp_list() {
+	$list = array('none' => 'none');
+	foreach (get_configured_vip_list('all', VIP_CARP) as $vid => $vaddr) {
+		$vip = get_configured_vip($vid);
+		$list[$vid] = sprintf("VHID %s on %s (%s)", $vip['vhid'], convert_friendly_interface_to_friendly_descr($vip['interface']), $vaddr);
+	}
+	return ($list);
+}
+
+// Write the config and rc files
+function mdns_bridge_write_files() {
+	// Get the real interface names
+	$interfaces = array();
+	foreach (explode(',', config_get_path('installedpackages/mdns-bridge/active_interfaces')) as $interface) {
+		$interfaces[$interface] = get_real_interface($interface);
+	}
+
+	// Global section
+	$file_array = array();
+	$file_array[] = '# This file is generated by the pfSense mDNS Bridge package.';
+	$file_array[] = '# Do not edit this file, it will be overwritten automatically.';
+
+	$file_array[] = '[global]';
+	$file_array[] = 'interfaces = ' . implode(', ', $interfaces);
+
+	$ip_protocols = config_get_path('installedpackages/mdns-bridge/global_ip_protocols');
+	switch ($ip_protocols) {
+		case 'ipv4':
+			$file_array['global_ip_protocols'] = 'disable-ipv6 = yes';
+			break;
+		case 'ipv6':
+			$file_array['global_ip_protocols'] = 'disable-ipv4 = yes';
+			break;
+	}
+
+	$filter_type = config_get_path('installedpackages/mdns-bridge/global_filter_type');
+	$filter_list = config_get_path('installedpackages/mdns-bridge/global_filter_list');
+	switch ($filter_type) {
+		case 'allow':
+			$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+			break;
+		case 'deny':
+			$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+			break;
+		default:
+			$disable_packet_filtering = config_get_path('installedpackages/mdns-bridge/disable_packet_filtering');
+			if ($disable_packet_filtering)
+			{
+				$file_array['disable-packet-filtering'] = 'disable-packet-filtering = yes';
+			}
+	}
+
+
+	// Interface sections
+	foreach ($interfaces as $interface => $real_interface) {
+		$file_array[] = '';
+		$file_array[] = "[{$real_interface}]";
+
+		if (!$file_array['global_ip_protocols']) {
+			$ip_protocols = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/ip_protocols");
+			switch ($ip_protocols) {
+				case 'ipv4':
+					$file_array[] = 'disable-ipv6 = yes';
+					break;
+				case 'ipv6':
+					$file_array[] = 'disable-ipv4 = yes';
+					break;
+			}
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+	}
+	$file_array[] = '';
+
+	// Write the config file
+	if (!file_put_contents(MDNS_BRIDGE_CONF_FILE, implode("\n", $file_array))) {
+		log_error('ERROR: Could not write file ' . MDNS_BRIDGE_CONF_FILE);
+	}
+
+	// Write the rc file
+	$start = MDNS_BRIDGE_CMD . ' -s -c ' . MDNS_BRIDGE_CONF_FILE . ' -p ' . MDNS_BRIDGE_PID_FILE;
+	$stop = '/usr/bin/killall ' . MDNS_BRIDGE_SVC_NAME;
+	write_rcfile(array(
+		"file" => "mdns-bridge.sh",
+		"start" => $start,
+		"stop" => $stop
+		)
+	);
+}
+
+function mdns_bridge_sync_config() {
+	// Stop the service if it is currently running
+	if (is_service_running(MDNS_BRIDGE_SVC_NAME)) {
+		log_error("Stopping service mDNS Bridge");
+		stop_service(MDNS_BRIDGE_SVC_NAME);
+	}
+
+	// If the service is now disabled, remove the config and rc files and return
+	if (!mdns_bridge_enabled()) {
+		unlink_if_exists(MDNS_BRIDGE_RC_FILE);
+		unlink_if_exists(MDNS_BRIDGE_CONF_FILE);
+		return;
+	}
+	
+	// Write the config and rc files
+	mdns_bridge_write_files();
+
+	// Take no further action during platform boot
+	if (platform_booting()) {
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: dennypage
body: Is this function new? I don't see it being used in other packages.
Does it exist in the CE version?
created_at: 2025-03-07T04:22:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
+}
+
+// Get the CARP VHID in use by mDNS Bridge
+function mdns_bridge_get_carp_vhid() {
+	$carp_vhid = config_get_path('installedpackages/mdns-bridge/carp_vhid', 'none');
+	if ($carp_vhid != 'none') {
+		return $carp_vhid;
+	}
+	return null;
+}
+
+// Get a CARP VHID list for use in mdns_bridge.php
+function mdns_bridge_get_carp_list() {
+	$list = array('none' => 'none');
+	foreach (get_configured_vip_list('all', VIP_CARP) as $vid => $vaddr) {
+		$vip = get_configured_vip($vid);
+		$list[$vid] = sprintf("VHID %s on %s (%s)", $vip['vhid'], convert_friendly_interface_to_friendly_descr($vip['interface']), $vaddr);
+	}
+	return ($list);
+}
+
+// Write the config and rc files
+function mdns_bridge_write_files() {
+	// Get the real interface names
+	$interfaces = array();
+	foreach (explode(',', config_get_path('installedpackages/mdns-bridge/active_interfaces')) as $interface) {
+		$interfaces[$interface] = get_real_interface($interface);
+	}
+
+	// Global section
+	$file_array = array();
+	$file_array[] = '# This file is generated by the pfSense mDNS Bridge package.';
+	$file_array[] = '# Do not edit this file, it will be overwritten automatically.';
+
+	$file_array[] = '[global]';
+	$file_array[] = 'interfaces = ' . implode(', ', $interfaces);
+
+	$ip_protocols = config_get_path('installedpackages/mdns-bridge/global_ip_protocols');
+	switch ($ip_protocols) {
+		case 'ipv4':
+			$file_array['global_ip_protocols'] = 'disable-ipv6 = yes';
+			break;
+		case 'ipv6':
+			$file_array['global_ip_protocols'] = 'disable-ipv4 = yes';
+			break;
+	}
+
+	$filter_type = config_get_path('installedpackages/mdns-bridge/global_filter_type');
+	$filter_list = config_get_path('installedpackages/mdns-bridge/global_filter_list');
+	switch ($filter_type) {
+		case 'allow':
+			$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+			break;
+		case 'deny':
+			$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+			break;
+		default:
+			$disable_packet_filtering = config_get_path('installedpackages/mdns-bridge/disable_packet_filtering');
+			if ($disable_packet_filtering)
+			{
+				$file_array['disable-packet-filtering'] = 'disable-packet-filtering = yes';
+			}
+	}
+
+
+	// Interface sections
+	foreach ($interfaces as $interface => $real_interface) {
+		$file_array[] = '';
+		$file_array[] = "[{$real_interface}]";
+
+		if (!$file_array['global_ip_protocols']) {
+			$ip_protocols = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/ip_protocols");
+			switch ($ip_protocols) {
+				case 'ipv4':
+					$file_array[] = 'disable-ipv6 = yes';
+					break;
+				case 'ipv6':
+					$file_array[] = 'disable-ipv4 = yes';
+					break;
+			}
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+	}
+	$file_array[] = '';
+
+	// Write the config file
+	if (!file_put_contents(MDNS_BRIDGE_CONF_FILE, implode("\n", $file_array))) {
+		log_error('ERROR: Could not write file ' . MDNS_BRIDGE_CONF_FILE);
+	}
+
+	// Write the rc file
+	$start = MDNS_BRIDGE_CMD . ' -s -c ' . MDNS_BRIDGE_CONF_FILE . ' -p ' . MDNS_BRIDGE_PID_FILE;
+	$stop = '/usr/bin/killall ' . MDNS_BRIDGE_SVC_NAME;
+	write_rcfile(array(
+		"file" => "mdns-bridge.sh",
+		"start" => $start,
+		"stop" => $stop
+		)
+	);
+}
+
+function mdns_bridge_sync_config() {
+	// Stop the service if it is currently running
+	if (is_service_running(MDNS_BRIDGE_SVC_NAME)) {
+		log_error("Stopping service mDNS Bridge");
+		stop_service(MDNS_BRIDGE_SVC_NAME);
+	}
+
+	// If the service is now disabled, remove the config and rc files and return
+	if (!mdns_bridge_enabled()) {
+		unlink_if_exists(MDNS_BRIDGE_RC_FILE);
+		unlink_if_exists(MDNS_BRIDGE_CONF_FILE);
+		return;
+	}
+	
+	// Write the config and rc files
+	mdns_bridge_write_files();
+
+	// Take no further action during platform boot
+	if (platform_booting()) {
+		return;
+	}
+
+	// Check the CARP VHID if in use
+	$carp_vhid = mdns_bridge_get_carp_vhid();
+	if ($carp_vhid) {
+		$status = get_carp_interface_status($carp_vhid);
+		switch ($status) {
+			case 'MASTER':
+				break;
+
+			case 'BACKUP':
+			case 'INIT':
+				// If the VHID is still initializing, or in backup, don't start the service.
+				log_error("mDNS Bridge service not started due to CARP status ({$status})");
+				return;
+				break;
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: dennypage
body: Yes, that's correct. It's good coding practice to always have a break.
created_at: 2025-03-07T04:23:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
+}
+
+// Get the CARP VHID in use by mDNS Bridge
+function mdns_bridge_get_carp_vhid() {
+	$carp_vhid = config_get_path('installedpackages/mdns-bridge/carp_vhid', 'none');
+	if ($carp_vhid != 'none') {
+		return $carp_vhid;
+	}
+	return null;
+}
+
+// Get a CARP VHID list for use in mdns_bridge.php
+function mdns_bridge_get_carp_list() {
+	$list = array('none' => 'none');
+	foreach (get_configured_vip_list('all', VIP_CARP) as $vid => $vaddr) {
+		$vip = get_configured_vip($vid);
+		$list[$vid] = sprintf("VHID %s on %s (%s)", $vip['vhid'], convert_friendly_interface_to_friendly_descr($vip['interface']), $vaddr);
+	}
+	return ($list);
+}
+
+// Write the config and rc files
+function mdns_bridge_write_files() {
+	// Get the real interface names
+	$interfaces = array();
+	foreach (explode(',', config_get_path('installedpackages/mdns-bridge/active_interfaces')) as $interface) {
+		$interfaces[$interface] = get_real_interface($interface);
+	}
+
+	// Global section
+	$file_array = array();
+	$file_array[] = '# This file is generated by the pfSense mDNS Bridge package.';
+	$file_array[] = '# Do not edit this file, it will be overwritten automatically.';
+
+	$file_array[] = '[global]';
+	$file_array[] = 'interfaces = ' . implode(', ', $interfaces);
+
+	$ip_protocols = config_get_path('installedpackages/mdns-bridge/global_ip_protocols');
+	switch ($ip_protocols) {
+		case 'ipv4':
+			$file_array['global_ip_protocols'] = 'disable-ipv6 = yes';
+			break;
+		case 'ipv6':
+			$file_array['global_ip_protocols'] = 'disable-ipv4 = yes';
+			break;
+	}
+
+	$filter_type = config_get_path('installedpackages/mdns-bridge/global_filter_type');
+	$filter_list = config_get_path('installedpackages/mdns-bridge/global_filter_list');
+	switch ($filter_type) {
+		case 'allow':
+			$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+			break;
+		case 'deny':
+			$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+			break;
+		default:
+			$disable_packet_filtering = config_get_path('installedpackages/mdns-bridge/disable_packet_filtering');
+			if ($disable_packet_filtering)
+			{
+				$file_array['disable-packet-filtering'] = 'disable-packet-filtering = yes';
+			}
+	}
+
+
+	// Interface sections
+	foreach ($interfaces as $interface => $real_interface) {
+		$file_array[] = '';
+		$file_array[] = "[{$real_interface}]";
+
+		if (!$file_array['global_ip_protocols']) {
+			$ip_protocols = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/ip_protocols");
+			switch ($ip_protocols) {
+				case 'ipv4':
+					$file_array[] = 'disable-ipv6 = yes';
+					break;
+				case 'ipv6':
+					$file_array[] = 'disable-ipv4 = yes';
+					break;
+			}
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+	}
+	$file_array[] = '';
+
+	// Write the config file
+	if (!file_put_contents(MDNS_BRIDGE_CONF_FILE, implode("\n", $file_array))) {
+		log_error('ERROR: Could not write file ' . MDNS_BRIDGE_CONF_FILE);
+	}
+
+	// Write the rc file
+	$start = MDNS_BRIDGE_CMD . ' -s -c ' . MDNS_BRIDGE_CONF_FILE . ' -p ' . MDNS_BRIDGE_PID_FILE;
+	$stop = '/usr/bin/killall ' . MDNS_BRIDGE_SVC_NAME;
+	write_rcfile(array(
+		"file" => "mdns-bridge.sh",
+		"start" => $start,
+		"stop" => $stop
+		)
+	);
+}
+
+function mdns_bridge_sync_config() {
+	// Stop the service if it is currently running
+	if (is_service_running(MDNS_BRIDGE_SVC_NAME)) {
+		log_error("Stopping service mDNS Bridge");
+		stop_service(MDNS_BRIDGE_SVC_NAME);
+	}
+
+	// If the service is now disabled, remove the config and rc files and return
+	if (!mdns_bridge_enabled()) {
+		unlink_if_exists(MDNS_BRIDGE_RC_FILE);
+		unlink_if_exists(MDNS_BRIDGE_CONF_FILE);
+		return;
+	}
+	
+	// Write the config and rc files
+	mdns_bridge_write_files();
+
+	// Take no further action during platform boot
+	if (platform_booting()) {
+		return;
+	}
+
+	// Check the CARP VHID if in use
+	$carp_vhid = mdns_bridge_get_carp_vhid();
+	if ($carp_vhid) {
+		$status = get_carp_interface_status($carp_vhid);
+		switch ($status) {
+			case 'MASTER':
+				break;
+
+			case 'BACKUP':
+			case 'INIT':
+				// If the VHID is still initializing, or in backup, don't start the service.
+				log_error("mDNS Bridge service not started due to CARP status ({$status})");
+				return;
+				break;
+
+			default:
+				log_error("mDNS Bridge could not determine CARP status for VHID {$carp_vhid}: status = '{$status}'");
+		}
+
+	}
+
+	// Start the service
+	log_error("Starting service mDNS Bridge");
+	start_service(MDNS_BRIDGE_SVC_NAME);
+}
+
+// Handle CARP notifications
+function mdns_bridge_plugin_carp($pluginparams) {
+	global $config;
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: dennypage
body: Agreed. Sorry about that.
created_at: 2025-03-07T04:23:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: dennypage
body: Okay. 
created_at: 2025-03-07T04:26:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: dennypage
body: The assumption that "wan" is a wan interface is common convention in a lot of the code. It helps users from making mistakes.
created_at: 2025-03-07T05:24:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
+
+if ($_POST) {
+	unset($input_errors);
+	$pconfig = $_POST;
+
+	// Check for Avahi conflict
+	if ($pconfig['enable'] && $avahi_enabled) {
+		$input_errors[] = gettext('Avahi relfection must be disabled before enabling mDNS Bridge');
+	}
+
+	// Validate interfaces
+	if (count($pconfig['active_interfaces']) < 2) {
+		$input_errors[] = gettext('A minimum of two interfaces are required');
+	}
+
+	// Vaidate and normalize the global filter
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: dennypage
body: Okay.
created_at: 2025-03-07T05:25:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
+
+if ($_POST) {
+	unset($input_errors);
+	$pconfig = $_POST;
+
+	// Check for Avahi conflict
+	if ($pconfig['enable'] && $avahi_enabled) {
+		$input_errors[] = gettext('Avahi relfection must be disabled before enabling mDNS Bridge');
+	}
+
+	// Validate interfaces
+	if (count($pconfig['active_interfaces']) < 2) {
+		$input_errors[] = gettext('A minimum of two interfaces are required');
+	}
+
+	// Vaidate and normalize the global filter
+	if ($pconfig['global_filter_type'] != 'none' && trim($pconfig['global_filter_list']) == '') {
+		$pconfig['global_filter_type'] = 'none';
+		$pconfig['global_filter_list'] = '';
+	}
+	if ($pconfig['global_filter_type'] != 'none') {
+		$pconfig['disable_packet_filtering'] = false;
+		$filter_list = array();
+		foreach (explode(',', $pconfig['global_filter_list']) as $filter) {
+			$filter = trim($filter);
+			if (!is_domain($filter, false, false)) {
+				$input_errors[] = sprintf(gettext('Invalid domain in Global Filter List: "%1$s"'), $filter);
+			}
+			$filter_list[] = $filter;
+		}
+		$pconfig['global_filter_list'] = implode(', ', $filter_list);
+	}
+
+	// Validate and normalize the interface filters
+	foreach ($pconfig['active_interfaces'] as $interface) {
+		// Inbound filter
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none' && trim($pconfig['inbound_filter_list_' . $interface]) == '') {
+			$pconfig['inbound_filter_type_' . $interface] = 'none';
+			$pconfig['inbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['inbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Inbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['inbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+
+		// Outbound filter
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none' && trim($pconfig['outbound_filter_list_' . $interface]) == '') {
+			$pconfig['outbound_filter_type_' . $interface] = 'none';
+			$pconfig['outbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['outbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Outbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['outbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+	}
+
+	// Rebuild the interfaces array
+	foreach ($available_interfaces as $interface => $name) {
+		array_set_path($pconfig, "interfaces/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+	}
+
+	// Update the config
+	if (!$input_errors) {
+		// Global settings
+		config_set_path($path_enable, $pconfig['enable']);
+		config_set_path($path_carp_vhid, $pconfig['carp_vhid']);
+		config_set_path($path_active_interfaces, implode(',', $pconfig['active_interfaces']));
+		config_set_path($path_global_ip_protocols, $pconfig['global_ip_protocols']);
+		config_set_path($path_global_filter_type, $pconfig['global_filter_type']);
+		config_set_path($path_global_filter_list, $pconfig['global_filter_list']);
+		config_set_path($path_disable_packet_filtering, $pconfig['disable_packet_filtering']);
+
+		// Interface settings
+		foreach ($pconfig['active_interfaces'] as $interface) {
+			config_set_path("{$path_interfaces}/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+		}
+
+		// Write the config
+		write_config(gettext("mDNS Bridge settings changed"));
+
+		// Sync the running configuration
+		mdns_bridge_sync_config();
+	}
+}
+
+
+$ip_protocol_types = array(
+	'both' => gettext('IPv4 and IPv6'),
+	'ipv4' => gettext('IPv4 only'),
+	'ipv6' => gettext('IPv6 only') );
+
+$filter_types = array(
+	'none' => gettext('none'),
+	'allow' => gettext('Allow'),
+	'deny' => gettext('Deny') );
+
+$filter_help_text = gettext(
+	'Comma separated list of mDNS names. Most often, a name should ' .
+	'be a single label representing a service name such as ' .
+	'_printer, _ipp, _ipps, _airplay, _hap, _http or _ssh.');
+$filter_placeholder_text = gettext('name1, name2, name3');
+
+
+$pgtitle = array(gettext("Services"), gettext("mDNS Bridge"));
+include("head.inc");
+
+if ($input_errors) {
+	print_input_errors($input_errors);
+}
+
+
+$form = new Form;
+$section = new Form_Section('General Settings');
+
+// Enable
+$section->addInput(new Form_Checkbox(
+	'enable',
+	'Enable',
+	'Enable the mDNS Bridge daemon',
+	$pconfig['enable']
+));
+
+// CARP
+$section->addInput(new Form_Select(
+	'carp_vhid',
+	'CARP Status VHID',
+	$pconfig['carp_vhid'],
+	mdns_bridge_get_carp_list()
+))->setHelp(gettext('Used for HA MASTER/BACKUP status. mDNS Bridge will be started when the chosen VHID is in MASTER status, and stopped when in BACKUP status.'));
+
+// List of interfaces
+$section->addInput(new Form_Select(
+	'active_interfaces',
+	'*Interfaces',
+	$pconfig['active_interfaces'],
+	$available_interfaces,
+	true
+))->addClass('active_interfaces')->setHelp(gettext('Interfaces that the mDNS Bridge daemon will operate on. Two or more interfaces are required.'));
+$form->add($section);
+
+$section = new Form_Section('Global Settings');
+// Global IP protocol list
+$section->addInput(new Form_Select(
+	'global_ip_protocols',
+	'IP Protocols',
+	$pconfig['global_ip_protocols'],
+	$ip_protocol_types
+))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+
+
+// Global filter type
+$group = new Form_Group('Global Filter');
+$group->add(new Form_Select(
+	'global_filter_type',
+	null,
+	$pconfig['global_filter_type'],
+	$filter_types
+))->setHelp(gettext('The global filter is applied to incoming packets on all interfaces prior to any interface specific filters.'));
+$section->add($group);
+
+// Conditional text explaining the currently selected filter type
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_none');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('All mDNS names are allowed by default.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_allow');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_deny');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+
+$group = new Form_Group('Global Filter List');
+$group->addClass('sh_global_filter_list');
+$group->add(new Form_Input(
+	'global_filter_list',
+	null,
+	'text',
+	$pconfig['global_filter_list']
+))->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+$section->add($group);
+
+$form->add($section);
+
+// Interface sections
+foreach ($available_interfaces as $interface => $name) {
+	$interface_config = array_get_path($pconfig, "interfaces/{$interface}", []);
+
+	$section = new Form_Section($name . ' Settings');
+	$section->addClass('sh_interface');
+	$section->addClass('sh_interface_' . $interface);
+
+	// IP protocols
+	$group = new Form_Group('IP Protocols');
+	$group->addClass('sh_interface_protocols');
+	$group->add(new Form_Select(
+		'ip_protocols_' . $interface,
+		null,
+		$interface_config['ip_protocols'],
+		$ip_protocol_types
+	))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+	$section->add($group);
+
+	// Inbound filter type
+	$group = new Form_Group('Inbound Filter');
+	$group->add(new Form_Select(
+		'inbound_filter_type_' . $interface,
+		null,
+		$interface_config['inbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The inbound filter is applied to packets received on the interface following the global filter.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected inbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed inbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	$group = new Form_Group('Inbound Filter List');
+	$group->addClass('sh_interface_inbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'inbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'inbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+
+	// Outbound filter type
+	$group = new Form_Group('Outbound Filter');
+	$group->add(new Form_Select(
+		'outbound_filter_type_' . $interface,
+		null,
+		$interface_config['outbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The outbound filter is applied to packets prior to sending packets on the interface.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected outbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed outbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	// Outbound filter list
+	$group = new Form_Group('Outbound Filter List');
+	$group->addClass('sh_interface_outbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'outbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'outbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+	$form->add($section);
+}
+
+// Advanced option to Disable all packet filtering
+$section = new Form_Section('Advanced');
+$section->addClass('sh_disable_packet_filtering');
+$section->addInput(new Form_Checkbox(
+	'disable_packet_filtering',
+	'Disable Packet Filtering',
+	'Completely disable packet decoding and filtering',
+	$pconfig['disable_packet_filtering']
+))->setHelp(gettext('Selecting this option will cause packets received from one interface to be forwarded directly to neighboring interfaces without any further processing. <b>This option disables all mDNS packet validation. Use with caution.</b>'));
+$form->add($section);
+
+print($form);
+?>
+
+
+<script type="text/javascript">
+//<![CDATA[
+events.push(function() {
+
+	// Show/hide interface sections
+	function hideInterfaces() {
+		hideClass('sh_interface', true);
+
+		var selected = $(".active_interfaces").val();
+		var length = $(".active_interfaces :selected").length;
+
+		for (var i=0; i<length; i++) {
+			hideClass('sh_interface_' + selected[i], false);
+		}
+	}
+
+	// Show/hide interface ip protocols based on global ip protocols
+	function hideInterfaceProtocols() {
+		hideClass('sh_interface_protocols', $('#global_ip_protocols').prop('value') != 'both');
+	}
+
+	// Show/hide the advanced
+	function hideAdvanced() {
+<?php
+		// This currently looks at all available interfaces. It might be preferable to only look at selected interfaces.
+		echo "showadvanced = ($('#global_filter_type').prop('value') == 'none')\n";
+		foreach ($available_interfaces as $interface => $name) {
+			echo " && $('#inbound_filter_type_{$interface}').prop('value') == 'none'\n";
+			echo " && $('#outbound_filter_type_{$interface}').prop('value') == 'none'\n";
+		}
+		echo ";\n";
+?>
+		hideClass('sh_disable_packet_filtering', !showadvanced);
+	}
+
+	// Show/hide based on global filter
+	function hideGlobalFilter() {
+		hideClass('sh_global_filter_none', $('#global_filter_type').prop('value') != 'none');
+		hideClass('sh_global_filter_allow', $('#global_filter_type').prop('value') != 'allow');
+		hideClass('sh_global_filter_deny', $('#global_filter_type').prop('value') != 'deny');
+		hideClass('sh_global_filter_list', $('#global_filter_type').prop('value') == 'none');
+	}
+
+<?php
+	// Show/hide based on interface filters
+	foreach ($available_interfaces as $interface => $name) {
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: dennypage
body: Given that the current code works, and has been tested, I'd prefer to stick with the current code.
created_at: 2025-03-07T05:28:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: marcos-ng
body: That assumption comes from the earlier days of the project. It's no longer true - though there are some places where the old code hasn't been fixed yet. It's wrong either way because it doesn't take into account scenarios with multi-WAN, router only (no WAN), nor interface reassignment.
created_at: 2025-03-07T13:55:03Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
+
+if ($_POST) {
+	unset($input_errors);
+	$pconfig = $_POST;
+
+	// Check for Avahi conflict
+	if ($pconfig['enable'] && $avahi_enabled) {
+		$input_errors[] = gettext('Avahi relfection must be disabled before enabling mDNS Bridge');
+	}
+
+	// Validate interfaces
+	if (count($pconfig['active_interfaces']) < 2) {
+		$input_errors[] = gettext('A minimum of two interfaces are required');
+	}
+
+	// Vaidate and normalize the global filter
+	if ($pconfig['global_filter_type'] != 'none' && trim($pconfig['global_filter_list']) == '') {
+		$pconfig['global_filter_type'] = 'none';
+		$pconfig['global_filter_list'] = '';
+	}
+	if ($pconfig['global_filter_type'] != 'none') {
+		$pconfig['disable_packet_filtering'] = false;
+		$filter_list = array();
+		foreach (explode(',', $pconfig['global_filter_list']) as $filter) {
+			$filter = trim($filter);
+			if (!is_domain($filter, false, false)) {
+				$input_errors[] = sprintf(gettext('Invalid domain in Global Filter List: "%1$s"'), $filter);
+			}
+			$filter_list[] = $filter;
+		}
+		$pconfig['global_filter_list'] = implode(', ', $filter_list);
+	}
+
+	// Validate and normalize the interface filters
+	foreach ($pconfig['active_interfaces'] as $interface) {
+		// Inbound filter
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none' && trim($pconfig['inbound_filter_list_' . $interface]) == '') {
+			$pconfig['inbound_filter_type_' . $interface] = 'none';
+			$pconfig['inbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['inbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Inbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['inbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+
+		// Outbound filter
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none' && trim($pconfig['outbound_filter_list_' . $interface]) == '') {
+			$pconfig['outbound_filter_type_' . $interface] = 'none';
+			$pconfig['outbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['outbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Outbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['outbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+	}
+
+	// Rebuild the interfaces array
+	foreach ($available_interfaces as $interface => $name) {
+		array_set_path($pconfig, "interfaces/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+	}
+
+	// Update the config
+	if (!$input_errors) {
+		// Global settings
+		config_set_path($path_enable, $pconfig['enable']);
+		config_set_path($path_carp_vhid, $pconfig['carp_vhid']);
+		config_set_path($path_active_interfaces, implode(',', $pconfig['active_interfaces']));
+		config_set_path($path_global_ip_protocols, $pconfig['global_ip_protocols']);
+		config_set_path($path_global_filter_type, $pconfig['global_filter_type']);
+		config_set_path($path_global_filter_list, $pconfig['global_filter_list']);
+		config_set_path($path_disable_packet_filtering, $pconfig['disable_packet_filtering']);
+
+		// Interface settings
+		foreach ($pconfig['active_interfaces'] as $interface) {
+			config_set_path("{$path_interfaces}/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+		}
+
+		// Write the config
+		write_config(gettext("mDNS Bridge settings changed"));
+
+		// Sync the running configuration
+		mdns_bridge_sync_config();
+	}
+}
+
+
+$ip_protocol_types = array(
+	'both' => gettext('IPv4 and IPv6'),
+	'ipv4' => gettext('IPv4 only'),
+	'ipv6' => gettext('IPv6 only') );
+
+$filter_types = array(
+	'none' => gettext('none'),
+	'allow' => gettext('Allow'),
+	'deny' => gettext('Deny') );
+
+$filter_help_text = gettext(
+	'Comma separated list of mDNS names. Most often, a name should ' .
+	'be a single label representing a service name such as ' .
+	'_printer, _ipp, _ipps, _airplay, _hap, _http or _ssh.');
+$filter_placeholder_text = gettext('name1, name2, name3');
+
+
+$pgtitle = array(gettext("Services"), gettext("mDNS Bridge"));
+include("head.inc");
+
+if ($input_errors) {
+	print_input_errors($input_errors);
+}
+
+
+$form = new Form;
+$section = new Form_Section('General Settings');
+
+// Enable
+$section->addInput(new Form_Checkbox(
+	'enable',
+	'Enable',
+	'Enable the mDNS Bridge daemon',
+	$pconfig['enable']
+));
+
+// CARP
+$section->addInput(new Form_Select(
+	'carp_vhid',
+	'CARP Status VHID',
+	$pconfig['carp_vhid'],
+	mdns_bridge_get_carp_list()
+))->setHelp(gettext('Used for HA MASTER/BACKUP status. mDNS Bridge will be started when the chosen VHID is in MASTER status, and stopped when in BACKUP status.'));
+
+// List of interfaces
+$section->addInput(new Form_Select(
+	'active_interfaces',
+	'*Interfaces',
+	$pconfig['active_interfaces'],
+	$available_interfaces,
+	true
+))->addClass('active_interfaces')->setHelp(gettext('Interfaces that the mDNS Bridge daemon will operate on. Two or more interfaces are required.'));
+$form->add($section);
+
+$section = new Form_Section('Global Settings');
+// Global IP protocol list
+$section->addInput(new Form_Select(
+	'global_ip_protocols',
+	'IP Protocols',
+	$pconfig['global_ip_protocols'],
+	$ip_protocol_types
+))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+
+
+// Global filter type
+$group = new Form_Group('Global Filter');
+$group->add(new Form_Select(
+	'global_filter_type',
+	null,
+	$pconfig['global_filter_type'],
+	$filter_types
+))->setHelp(gettext('The global filter is applied to incoming packets on all interfaces prior to any interface specific filters.'));
+$section->add($group);
+
+// Conditional text explaining the currently selected filter type
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_none');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('All mDNS names are allowed by default.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_allow');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_deny');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+
+$group = new Form_Group('Global Filter List');
+$group->addClass('sh_global_filter_list');
+$group->add(new Form_Input(
+	'global_filter_list',
+	null,
+	'text',
+	$pconfig['global_filter_list']
+))->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+$section->add($group);
+
+$form->add($section);
+
+// Interface sections
+foreach ($available_interfaces as $interface => $name) {
+	$interface_config = array_get_path($pconfig, "interfaces/{$interface}", []);
+
+	$section = new Form_Section($name . ' Settings');
+	$section->addClass('sh_interface');
+	$section->addClass('sh_interface_' . $interface);
+
+	// IP protocols
+	$group = new Form_Group('IP Protocols');
+	$group->addClass('sh_interface_protocols');
+	$group->add(new Form_Select(
+		'ip_protocols_' . $interface,
+		null,
+		$interface_config['ip_protocols'],
+		$ip_protocol_types
+	))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+	$section->add($group);
+
+	// Inbound filter type
+	$group = new Form_Group('Inbound Filter');
+	$group->add(new Form_Select(
+		'inbound_filter_type_' . $interface,
+		null,
+		$interface_config['inbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The inbound filter is applied to packets received on the interface following the global filter.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected inbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed inbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	$group = new Form_Group('Inbound Filter List');
+	$group->addClass('sh_interface_inbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'inbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'inbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+
+	// Outbound filter type
+	$group = new Form_Group('Outbound Filter');
+	$group->add(new Form_Select(
+		'outbound_filter_type_' . $interface,
+		null,
+		$interface_config['outbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The outbound filter is applied to packets prior to sending packets on the interface.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected outbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed outbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	// Outbound filter list
+	$group = new Form_Group('Outbound Filter List');
+	$group->addClass('sh_interface_outbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'outbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'outbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+	$form->add($section);
+}
+
+// Advanced option to Disable all packet filtering
+$section = new Form_Section('Advanced');
+$section->addClass('sh_disable_packet_filtering');
+$section->addInput(new Form_Checkbox(
+	'disable_packet_filtering',
+	'Disable Packet Filtering',
+	'Completely disable packet decoding and filtering',
+	$pconfig['disable_packet_filtering']
+))->setHelp(gettext('Selecting this option will cause packets received from one interface to be forwarded directly to neighboring interfaces without any further processing. <b>This option disables all mDNS packet validation. Use with caution.</b>'));
+$form->add($section);
+
+print($form);
+?>
+
+
+<script type="text/javascript">
+//<![CDATA[
+events.push(function() {
+
+	// Show/hide interface sections
+	function hideInterfaces() {
+		hideClass('sh_interface', true);
+
+		var selected = $(".active_interfaces").val();
+		var length = $(".active_interfaces :selected").length;
+
+		for (var i=0; i<length; i++) {
+			hideClass('sh_interface_' + selected[i], false);
+		}
+	}
+
+	// Show/hide interface ip protocols based on global ip protocols
+	function hideInterfaceProtocols() {
+		hideClass('sh_interface_protocols', $('#global_ip_protocols').prop('value') != 'both');
+	}
+
+	// Show/hide the advanced
+	function hideAdvanced() {
+<?php
+		// This currently looks at all available interfaces. It might be preferable to only look at selected interfaces.
+		echo "showadvanced = ($('#global_filter_type').prop('value') == 'none')\n";
+		foreach ($available_interfaces as $interface => $name) {
+			echo " && $('#inbound_filter_type_{$interface}').prop('value') == 'none'\n";
+			echo " && $('#outbound_filter_type_{$interface}').prop('value') == 'none'\n";
+		}
+		echo ";\n";
+?>
+		hideClass('sh_disable_packet_filtering', !showadvanced);
+	}
+
+	// Show/hide based on global filter
+	function hideGlobalFilter() {
+		hideClass('sh_global_filter_none', $('#global_filter_type').prop('value') != 'none');
+		hideClass('sh_global_filter_allow', $('#global_filter_type').prop('value') != 'allow');
+		hideClass('sh_global_filter_deny', $('#global_filter_type').prop('value') != 'deny');
+		hideClass('sh_global_filter_list', $('#global_filter_type').prop('value') == 'none');
+	}
+
+<?php
+	// Show/hide based on interface filters
+	foreach ($available_interfaces as $interface => $name) {
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: marcos-ng
body: The downside with echo'ing all the code is that it makes it more difficult to maintain since things like linters won't catch any issues. Using JS as described reduces code duplication and allows for code tools to work as expected.
created_at: 2025-03-07T14:07:45Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
+}
+
+// Get the CARP VHID in use by mDNS Bridge
+function mdns_bridge_get_carp_vhid() {
+	$carp_vhid = config_get_path('installedpackages/mdns-bridge/carp_vhid', 'none');
+	if ($carp_vhid != 'none') {
+		return $carp_vhid;
+	}
+	return null;
+}
+
+// Get a CARP VHID list for use in mdns_bridge.php
+function mdns_bridge_get_carp_list() {
+	$list = array('none' => 'none');
+	foreach (get_configured_vip_list('all', VIP_CARP) as $vid => $vaddr) {
+		$vip = get_configured_vip($vid);
+		$list[$vid] = sprintf("VHID %s on %s (%s)", $vip['vhid'], convert_friendly_interface_to_friendly_descr($vip['interface']), $vaddr);
+	}
+	return ($list);
+}
+
+// Write the config and rc files
+function mdns_bridge_write_files() {
+	// Get the real interface names
+	$interfaces = array();
+	foreach (explode(',', config_get_path('installedpackages/mdns-bridge/active_interfaces')) as $interface) {
+		$interfaces[$interface] = get_real_interface($interface);
+	}
+
+	// Global section
+	$file_array = array();
+	$file_array[] = '# This file is generated by the pfSense mDNS Bridge package.';
+	$file_array[] = '# Do not edit this file, it will be overwritten automatically.';
+
+	$file_array[] = '[global]';
+	$file_array[] = 'interfaces = ' . implode(', ', $interfaces);
+
+	$ip_protocols = config_get_path('installedpackages/mdns-bridge/global_ip_protocols');
+	switch ($ip_protocols) {
+		case 'ipv4':
+			$file_array['global_ip_protocols'] = 'disable-ipv6 = yes';
+			break;
+		case 'ipv6':
+			$file_array['global_ip_protocols'] = 'disable-ipv4 = yes';
+			break;
+	}
+
+	$filter_type = config_get_path('installedpackages/mdns-bridge/global_filter_type');
+	$filter_list = config_get_path('installedpackages/mdns-bridge/global_filter_list');
+	switch ($filter_type) {
+		case 'allow':
+			$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+			break;
+		case 'deny':
+			$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+			break;
+		default:
+			$disable_packet_filtering = config_get_path('installedpackages/mdns-bridge/disable_packet_filtering');
+			if ($disable_packet_filtering)
+			{
+				$file_array['disable-packet-filtering'] = 'disable-packet-filtering = yes';
+			}
+	}
+
+
+	// Interface sections
+	foreach ($interfaces as $interface => $real_interface) {
+		$file_array[] = '';
+		$file_array[] = "[{$real_interface}]";
+
+		if (!$file_array['global_ip_protocols']) {
+			$ip_protocols = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/ip_protocols");
+			switch ($ip_protocols) {
+				case 'ipv4':
+					$file_array[] = 'disable-ipv6 = yes';
+					break;
+				case 'ipv6':
+					$file_array[] = 'disable-ipv4 = yes';
+					break;
+			}
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+	}
+	$file_array[] = '';
+
+	// Write the config file
+	if (!file_put_contents(MDNS_BRIDGE_CONF_FILE, implode("\n", $file_array))) {
+		log_error('ERROR: Could not write file ' . MDNS_BRIDGE_CONF_FILE);
+	}
+
+	// Write the rc file
+	$start = MDNS_BRIDGE_CMD . ' -s -c ' . MDNS_BRIDGE_CONF_FILE . ' -p ' . MDNS_BRIDGE_PID_FILE;
+	$stop = '/usr/bin/killall ' . MDNS_BRIDGE_SVC_NAME;
+	write_rcfile(array(
+		"file" => "mdns-bridge.sh",
+		"start" => $start,
+		"stop" => $stop
+		)
+	);
+}
+
+function mdns_bridge_sync_config() {
+	// Stop the service if it is currently running
+	if (is_service_running(MDNS_BRIDGE_SVC_NAME)) {
+		log_error("Stopping service mDNS Bridge");
+		stop_service(MDNS_BRIDGE_SVC_NAME);
+	}
+
+	// If the service is now disabled, remove the config and rc files and return
+	if (!mdns_bridge_enabled()) {
+		unlink_if_exists(MDNS_BRIDGE_RC_FILE);
+		unlink_if_exists(MDNS_BRIDGE_CONF_FILE);
+		return;
+	}
+	
+	// Write the config and rc files
+	mdns_bridge_write_files();
+
+	// Take no further action during platform boot
+	if (platform_booting()) {
+		return;
+	}
+
+	// Check the CARP VHID if in use
+	$carp_vhid = mdns_bridge_get_carp_vhid();
+	if ($carp_vhid) {
+		$status = get_carp_interface_status($carp_vhid);
+		switch ($status) {
+			case 'MASTER':
+				break;
+
+			case 'BACKUP':
+			case 'INIT':
+				// If the VHID is still initializing, or in backup, don't start the service.
+				log_error("mDNS Bridge service not started due to CARP status ({$status})");
+				return;
+				break;
+
+			default:
+				log_error("mDNS Bridge could not determine CARP status for VHID {$carp_vhid}: status = '{$status}'");
+		}
+
+	}
+
+	// Start the service
+	log_error("Starting service mDNS Bridge");
+	start_service(MDNS_BRIDGE_SVC_NAME);
+}
+
+// Handle CARP notifications
+function mdns_bridge_plugin_carp($pluginparams) {
+	global $config;
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: marcos-ng
body: No worries, no need to apologize :)
created_at: 2025-03-07T14:11:39Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
+}
+
+// Get the CARP VHID in use by mDNS Bridge
+function mdns_bridge_get_carp_vhid() {
+	$carp_vhid = config_get_path('installedpackages/mdns-bridge/carp_vhid', 'none');
+	if ($carp_vhid != 'none') {
+		return $carp_vhid;
+	}
+	return null;
+}
+
+// Get a CARP VHID list for use in mdns_bridge.php
+function mdns_bridge_get_carp_list() {
+	$list = array('none' => 'none');
+	foreach (get_configured_vip_list('all', VIP_CARP) as $vid => $vaddr) {
+		$vip = get_configured_vip($vid);
+		$list[$vid] = sprintf("VHID %s on %s (%s)", $vip['vhid'], convert_friendly_interface_to_friendly_descr($vip['interface']), $vaddr);
+	}
+	return ($list);
+}
+
+// Write the config and rc files
+function mdns_bridge_write_files() {
+	// Get the real interface names
+	$interfaces = array();
+	foreach (explode(',', config_get_path('installedpackages/mdns-bridge/active_interfaces')) as $interface) {
+		$interfaces[$interface] = get_real_interface($interface);
+	}
+
+	// Global section
+	$file_array = array();
+	$file_array[] = '# This file is generated by the pfSense mDNS Bridge package.';
+	$file_array[] = '# Do not edit this file, it will be overwritten automatically.';
+
+	$file_array[] = '[global]';
+	$file_array[] = 'interfaces = ' . implode(', ', $interfaces);
+
+	$ip_protocols = config_get_path('installedpackages/mdns-bridge/global_ip_protocols');
+	switch ($ip_protocols) {
+		case 'ipv4':
+			$file_array['global_ip_protocols'] = 'disable-ipv6 = yes';
+			break;
+		case 'ipv6':
+			$file_array['global_ip_protocols'] = 'disable-ipv4 = yes';
+			break;
+	}
+
+	$filter_type = config_get_path('installedpackages/mdns-bridge/global_filter_type');
+	$filter_list = config_get_path('installedpackages/mdns-bridge/global_filter_list');
+	switch ($filter_type) {
+		case 'allow':
+			$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+			break;
+		case 'deny':
+			$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+			break;
+		default:
+			$disable_packet_filtering = config_get_path('installedpackages/mdns-bridge/disable_packet_filtering');
+			if ($disable_packet_filtering)
+			{
+				$file_array['disable-packet-filtering'] = 'disable-packet-filtering = yes';
+			}
+	}
+
+
+	// Interface sections
+	foreach ($interfaces as $interface => $real_interface) {
+		$file_array[] = '';
+		$file_array[] = "[{$real_interface}]";
+
+		if (!$file_array['global_ip_protocols']) {
+			$ip_protocols = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/ip_protocols");
+			switch ($ip_protocols) {
+				case 'ipv4':
+					$file_array[] = 'disable-ipv6 = yes';
+					break;
+				case 'ipv6':
+					$file_array[] = 'disable-ipv4 = yes';
+					break;
+			}
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+	}
+	$file_array[] = '';
+
+	// Write the config file
+	if (!file_put_contents(MDNS_BRIDGE_CONF_FILE, implode("\n", $file_array))) {
+		log_error('ERROR: Could not write file ' . MDNS_BRIDGE_CONF_FILE);
+	}
+
+	// Write the rc file
+	$start = MDNS_BRIDGE_CMD . ' -s -c ' . MDNS_BRIDGE_CONF_FILE . ' -p ' . MDNS_BRIDGE_PID_FILE;
+	$stop = '/usr/bin/killall ' . MDNS_BRIDGE_SVC_NAME;
+	write_rcfile(array(
+		"file" => "mdns-bridge.sh",
+		"start" => $start,
+		"stop" => $stop
+		)
+	);
+}
+
+function mdns_bridge_sync_config() {
+	// Stop the service if it is currently running
+	if (is_service_running(MDNS_BRIDGE_SVC_NAME)) {
+		log_error("Stopping service mDNS Bridge");
+		stop_service(MDNS_BRIDGE_SVC_NAME);
+	}
+
+	// If the service is now disabled, remove the config and rc files and return
+	if (!mdns_bridge_enabled()) {
+		unlink_if_exists(MDNS_BRIDGE_RC_FILE);
+		unlink_if_exists(MDNS_BRIDGE_CONF_FILE);
+		return;
+	}
+	
+	// Write the config and rc files
+	mdns_bridge_write_files();
+
+	// Take no further action during platform boot
+	if (platform_booting()) {
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: marcos-ng
body: It's new-ish, and yes.
created_at: 2025-03-07T14:13:22Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
+}
+
+// Get the CARP VHID in use by mDNS Bridge
+function mdns_bridge_get_carp_vhid() {
+	$carp_vhid = config_get_path('installedpackages/mdns-bridge/carp_vhid', 'none');
+	if ($carp_vhid != 'none') {
+		return $carp_vhid;
+	}
+	return null;
+}
+
+// Get a CARP VHID list for use in mdns_bridge.php
+function mdns_bridge_get_carp_list() {
+	$list = array('none' => 'none');
+	foreach (get_configured_vip_list('all', VIP_CARP) as $vid => $vaddr) {
+		$vip = get_configured_vip($vid);
+		$list[$vid] = sprintf("VHID %s on %s (%s)", $vip['vhid'], convert_friendly_interface_to_friendly_descr($vip['interface']), $vaddr);
+	}
+	return ($list);
+}
+
+// Write the config and rc files
+function mdns_bridge_write_files() {
+	// Get the real interface names
+	$interfaces = array();
+	foreach (explode(',', config_get_path('installedpackages/mdns-bridge/active_interfaces')) as $interface) {
+		$interfaces[$interface] = get_real_interface($interface);
+	}
+
+	// Global section
+	$file_array = array();
+	$file_array[] = '# This file is generated by the pfSense mDNS Bridge package.';
+	$file_array[] = '# Do not edit this file, it will be overwritten automatically.';
+
+	$file_array[] = '[global]';
+	$file_array[] = 'interfaces = ' . implode(', ', $interfaces);
+
+	$ip_protocols = config_get_path('installedpackages/mdns-bridge/global_ip_protocols');
+	switch ($ip_protocols) {
+		case 'ipv4':
+			$file_array['global_ip_protocols'] = 'disable-ipv6 = yes';
+			break;
+		case 'ipv6':
+			$file_array['global_ip_protocols'] = 'disable-ipv4 = yes';
+			break;
+	}
+
+	$filter_type = config_get_path('installedpackages/mdns-bridge/global_filter_type');
+	$filter_list = config_get_path('installedpackages/mdns-bridge/global_filter_list');
+	switch ($filter_type) {
+		case 'allow':
+			$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+			break;
+		case 'deny':
+			$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+			break;
+		default:
+			$disable_packet_filtering = config_get_path('installedpackages/mdns-bridge/disable_packet_filtering');
+			if ($disable_packet_filtering)
+			{
+				$file_array['disable-packet-filtering'] = 'disable-packet-filtering = yes';
+			}
+	}
+
+
+	// Interface sections
+	foreach ($interfaces as $interface => $real_interface) {
+		$file_array[] = '';
+		$file_array[] = "[{$real_interface}]";
+
+		if (!$file_array['global_ip_protocols']) {
+			$ip_protocols = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/ip_protocols");
+			switch ($ip_protocols) {
+				case 'ipv4':
+					$file_array[] = 'disable-ipv6 = yes';
+					break;
+				case 'ipv6':
+					$file_array[] = 'disable-ipv4 = yes';
+					break;
+			}
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+	}
+	$file_array[] = '';
+
+	// Write the config file
+	if (!file_put_contents(MDNS_BRIDGE_CONF_FILE, implode("\n", $file_array))) {
+		log_error('ERROR: Could not write file ' . MDNS_BRIDGE_CONF_FILE);
+	}
+
+	// Write the rc file
+	$start = MDNS_BRIDGE_CMD . ' -s -c ' . MDNS_BRIDGE_CONF_FILE . ' -p ' . MDNS_BRIDGE_PID_FILE;
+	$stop = '/usr/bin/killall ' . MDNS_BRIDGE_SVC_NAME;
+	write_rcfile(array(
+		"file" => "mdns-bridge.sh",
+		"start" => $start,
+		"stop" => $stop
+		)
+	);
+}
+
+function mdns_bridge_sync_config() {
+	// Stop the service if it is currently running
+	if (is_service_running(MDNS_BRIDGE_SVC_NAME)) {
+		log_error("Stopping service mDNS Bridge");
+		stop_service(MDNS_BRIDGE_SVC_NAME);
+	}
+
+	// If the service is now disabled, remove the config and rc files and return
+	if (!mdns_bridge_enabled()) {
+		unlink_if_exists(MDNS_BRIDGE_RC_FILE);
+		unlink_if_exists(MDNS_BRIDGE_CONF_FILE);
+		return;
+	}
+	
+	// Write the config and rc files
+	mdns_bridge_write_files();
+
+	// Take no further action during platform boot
+	if (platform_booting()) {
+		return;
+	}
+
+	// Check the CARP VHID if in use
+	$carp_vhid = mdns_bridge_get_carp_vhid();
+	if ($carp_vhid) {
+		$status = get_carp_interface_status($carp_vhid);
+		switch ($status) {
+			case 'MASTER':
+				break;
+
+			case 'BACKUP':
+			case 'INIT':
+				// If the VHID is still initializing, or in backup, don't start the service.
+				log_error("mDNS Bridge service not started due to CARP status ({$status})");
+				return;
+				break;
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: marcos-ng
body: Depends on the person I suppose. I disagree - it's like writing `break;` twice. Keep it if you'd prefer though.
created_at: 2025-03-07T14:23:40Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
+}
+
+// Get the CARP VHID in use by mDNS Bridge
+function mdns_bridge_get_carp_vhid() {
+	$carp_vhid = config_get_path('installedpackages/mdns-bridge/carp_vhid', 'none');
+	if ($carp_vhid != 'none') {
+		return $carp_vhid;
+	}
+	return null;
+}
+
+// Get a CARP VHID list for use in mdns_bridge.php
+function mdns_bridge_get_carp_list() {
+	$list = array('none' => 'none');
+	foreach (get_configured_vip_list('all', VIP_CARP) as $vid => $vaddr) {
+		$vip = get_configured_vip($vid);
+		$list[$vid] = sprintf("VHID %s on %s (%s)", $vip['vhid'], convert_friendly_interface_to_friendly_descr($vip['interface']), $vaddr);
+	}
+	return ($list);
+}
+
+// Write the config and rc files
+function mdns_bridge_write_files() {
+	// Get the real interface names
+	$interfaces = array();
+	foreach (explode(',', config_get_path('installedpackages/mdns-bridge/active_interfaces')) as $interface) {
+		$interfaces[$interface] = get_real_interface($interface);
+	}
+
+	// Global section
+	$file_array = array();
+	$file_array[] = '# This file is generated by the pfSense mDNS Bridge package.';
+	$file_array[] = '# Do not edit this file, it will be overwritten automatically.';
+
+	$file_array[] = '[global]';
+	$file_array[] = 'interfaces = ' . implode(', ', $interfaces);
+
+	$ip_protocols = config_get_path('installedpackages/mdns-bridge/global_ip_protocols');
+	switch ($ip_protocols) {
+		case 'ipv4':
+			$file_array['global_ip_protocols'] = 'disable-ipv6 = yes';
+			break;
+		case 'ipv6':
+			$file_array['global_ip_protocols'] = 'disable-ipv4 = yes';
+			break;
+	}
+
+	$filter_type = config_get_path('installedpackages/mdns-bridge/global_filter_type');
+	$filter_list = config_get_path('installedpackages/mdns-bridge/global_filter_list');
+	switch ($filter_type) {
+		case 'allow':
+			$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+			break;
+		case 'deny':
+			$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+			break;
+		default:
+			$disable_packet_filtering = config_get_path('installedpackages/mdns-bridge/disable_packet_filtering');
+			if ($disable_packet_filtering)
+			{
+				$file_array['disable-packet-filtering'] = 'disable-packet-filtering = yes';
+			}
+	}
+
+
+	// Interface sections
+	foreach ($interfaces as $interface => $real_interface) {
+		$file_array[] = '';
+		$file_array[] = "[{$real_interface}]";
+
+		if (!$file_array['global_ip_protocols']) {
+			$ip_protocols = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/ip_protocols");
+			switch ($ip_protocols) {
+				case 'ipv4':
+					$file_array[] = 'disable-ipv6 = yes';
+					break;
+				case 'ipv6':
+					$file_array[] = 'disable-ipv4 = yes';
+					break;
+			}
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/inbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-inbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+
+		$filter_type = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_type");
+		$filter_list = config_get_path("installedpackages/mdns-bridge/interfaces/{$interface}/outbound_filter_list");
+		switch ($filter_type) {
+			case 'allow':
+				$file_array[] = 'allow-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+			case 'deny':
+				$file_array[] = 'deny-outbound-filters = ' . $filter_list;
+				unset($file_array['disable-packet-filtering']);
+				break;
+		}
+	}
+	$file_array[] = '';
+
+	// Write the config file
+	if (!file_put_contents(MDNS_BRIDGE_CONF_FILE, implode("\n", $file_array))) {
+		log_error('ERROR: Could not write file ' . MDNS_BRIDGE_CONF_FILE);
+	}
+
+	// Write the rc file
+	$start = MDNS_BRIDGE_CMD . ' -s -c ' . MDNS_BRIDGE_CONF_FILE . ' -p ' . MDNS_BRIDGE_PID_FILE;
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: marcos-ng
body: I mention it because it's part of the code style guidelines in the docs. I do agree it can have readability benefits though. Keep it if you prefer.
created_at: 2025-03-07T14:26:42Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: dennypage
body: Looking at the php7 -> php8 guide, it seems that the recommendation was to avoid config_path_enabled() because there are usages that it does not interpret correctly. The current mDNS behavior is one of these.

Couple of additional things that bother me about this... it really should be a set method either way. Having 'on' be a set method, and 'off' be a delete method makes the code harder to read and less reliable. It also requires custom code in the www file to detect that the pconfig key is false, and then call delete for the config rather than simply setting the config to match pconfig. This does not seem desirable. Lastly, it would break the beta tester installs.

FWIW, I used the config_get_path pattern when I ported my other packages to php8, and it was accepted without issue.
created_at: 2025-03-07T18:08:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
+}
+
+// Get the CARP VHID in use by mDNS Bridge
+function mdns_bridge_get_carp_vhid() {
+	$carp_vhid = config_get_path('installedpackages/mdns-bridge/carp_vhid', 'none');
+	if ($carp_vhid != 'none') {
+		return $carp_vhid;
+	}
+	return null;
+}
+
+// Get a CARP VHID list for use in mdns_bridge.php
+function mdns_bridge_get_carp_list() {
+	$list = array('none' => 'none');
+	foreach (get_configured_vip_list('all', VIP_CARP) as $vid => $vaddr) {
+		$vip = get_configured_vip($vid);
+		$list[$vid] = sprintf("VHID %s on %s (%s)", $vip['vhid'], convert_friendly_interface_to_friendly_descr($vip['interface']), $vaddr);
+	}
+	return ($list);
+}
+
+// Write the config and rc files
+function mdns_bridge_write_files() {
+	// Get the real interface names
+	$interfaces = array();
+	foreach (explode(',', config_get_path('installedpackages/mdns-bridge/active_interfaces')) as $interface) {
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: dennypage
body: Good catch, thanks.
created_at: 2025-03-07T18:18:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: dennypage
body: FWIW, there are a lot of places, both pfSense proper and packages, that assume a meaning for the internal names 'lan' and 'wan'. I'm okay deleting the default of 'lan' here because it doesn't hurt anything, but it does seem to go against a strong convention.
created_at: 2025-03-07T18:34:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: marcos-ng
body: The function `config_path_enabled()` is only intended to be used with bool-like config elements. It can also be used to check the existence of some configuration; indeed there are places where this is done which are likely to be fixed as the API progresses. There are also bool-like config elements that store the state as a string (e.g. 'on/off', 'yes/no', etc.) rather than using the existence of the element as 'on'. These are the cases where the function is not appropriate.

You are free to set the bool-like element to false, but note that it will simply be unset when the configuration is written to the file - and hence not exist once the configuration is read back.
created_at: 2025-03-07T18:38:16Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: dennypage
body: I understand that there may be other WAN interfaces, but there isn't a way to detect that. There are a LOT of places that assume 'wan' should be excluded from interface selections. Unless there is a function to call such as is_interface_wan(), I don't see any reasonable way to implement. And since 'wan' will always be a wan interface, I don't see any reason to not exclude it. This is a good practice to help the 80% user from making an egregious mistake.
created_at: 2025-03-07T18:41:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
+
+if ($_POST) {
+	unset($input_errors);
+	$pconfig = $_POST;
+
+	// Check for Avahi conflict
+	if ($pconfig['enable'] && $avahi_enabled) {
+		$input_errors[] = gettext('Avahi relfection must be disabled before enabling mDNS Bridge');
+	}
+
+	// Validate interfaces
+	if (count($pconfig['active_interfaces']) < 2) {
+		$input_errors[] = gettext('A minimum of two interfaces are required');
+	}
+
+	// Vaidate and normalize the global filter
+	if ($pconfig['global_filter_type'] != 'none' && trim($pconfig['global_filter_list']) == '') {
+		$pconfig['global_filter_type'] = 'none';
+		$pconfig['global_filter_list'] = '';
+	}
+	if ($pconfig['global_filter_type'] != 'none') {
+		$pconfig['disable_packet_filtering'] = false;
+		$filter_list = array();
+		foreach (explode(',', $pconfig['global_filter_list']) as $filter) {
+			$filter = trim($filter);
+			if (!is_domain($filter, false, false)) {
+				$input_errors[] = sprintf(gettext('Invalid domain in Global Filter List: "%1$s"'), $filter);
+			}
+			$filter_list[] = $filter;
+		}
+		$pconfig['global_filter_list'] = implode(', ', $filter_list);
+	}
+
+	// Validate and normalize the interface filters
+	foreach ($pconfig['active_interfaces'] as $interface) {
+		// Inbound filter
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none' && trim($pconfig['inbound_filter_list_' . $interface]) == '') {
+			$pconfig['inbound_filter_type_' . $interface] = 'none';
+			$pconfig['inbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['inbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Inbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['inbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+
+		// Outbound filter
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none' && trim($pconfig['outbound_filter_list_' . $interface]) == '') {
+			$pconfig['outbound_filter_type_' . $interface] = 'none';
+			$pconfig['outbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['outbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Outbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['outbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+	}
+
+	// Rebuild the interfaces array
+	foreach ($available_interfaces as $interface => $name) {
+		array_set_path($pconfig, "interfaces/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+	}
+
+	// Update the config
+	if (!$input_errors) {
+		// Global settings
+		config_set_path($path_enable, $pconfig['enable']);
+		config_set_path($path_carp_vhid, $pconfig['carp_vhid']);
+		config_set_path($path_active_interfaces, implode(',', $pconfig['active_interfaces']));
+		config_set_path($path_global_ip_protocols, $pconfig['global_ip_protocols']);
+		config_set_path($path_global_filter_type, $pconfig['global_filter_type']);
+		config_set_path($path_global_filter_list, $pconfig['global_filter_list']);
+		config_set_path($path_disable_packet_filtering, $pconfig['disable_packet_filtering']);
+
+		// Interface settings
+		foreach ($pconfig['active_interfaces'] as $interface) {
+			config_set_path("{$path_interfaces}/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+		}
+
+		// Write the config
+		write_config(gettext("mDNS Bridge settings changed"));
+
+		// Sync the running configuration
+		mdns_bridge_sync_config();
+	}
+}
+
+
+$ip_protocol_types = array(
+	'both' => gettext('IPv4 and IPv6'),
+	'ipv4' => gettext('IPv4 only'),
+	'ipv6' => gettext('IPv6 only') );
+
+$filter_types = array(
+	'none' => gettext('none'),
+	'allow' => gettext('Allow'),
+	'deny' => gettext('Deny') );
+
+$filter_help_text = gettext(
+	'Comma separated list of mDNS names. Most often, a name should ' .
+	'be a single label representing a service name such as ' .
+	'_printer, _ipp, _ipps, _airplay, _hap, _http or _ssh.');
+$filter_placeholder_text = gettext('name1, name2, name3');
+
+
+$pgtitle = array(gettext("Services"), gettext("mDNS Bridge"));
+include("head.inc");
+
+if ($input_errors) {
+	print_input_errors($input_errors);
+}
+
+
+$form = new Form;
+$section = new Form_Section('General Settings');
+
+// Enable
+$section->addInput(new Form_Checkbox(
+	'enable',
+	'Enable',
+	'Enable the mDNS Bridge daemon',
+	$pconfig['enable']
+));
+
+// CARP
+$section->addInput(new Form_Select(
+	'carp_vhid',
+	'CARP Status VHID',
+	$pconfig['carp_vhid'],
+	mdns_bridge_get_carp_list()
+))->setHelp(gettext('Used for HA MASTER/BACKUP status. mDNS Bridge will be started when the chosen VHID is in MASTER status, and stopped when in BACKUP status.'));
+
+// List of interfaces
+$section->addInput(new Form_Select(
+	'active_interfaces',
+	'*Interfaces',
+	$pconfig['active_interfaces'],
+	$available_interfaces,
+	true
+))->addClass('active_interfaces')->setHelp(gettext('Interfaces that the mDNS Bridge daemon will operate on. Two or more interfaces are required.'));
+$form->add($section);
+
+$section = new Form_Section('Global Settings');
+// Global IP protocol list
+$section->addInput(new Form_Select(
+	'global_ip_protocols',
+	'IP Protocols',
+	$pconfig['global_ip_protocols'],
+	$ip_protocol_types
+))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+
+
+// Global filter type
+$group = new Form_Group('Global Filter');
+$group->add(new Form_Select(
+	'global_filter_type',
+	null,
+	$pconfig['global_filter_type'],
+	$filter_types
+))->setHelp(gettext('The global filter is applied to incoming packets on all interfaces prior to any interface specific filters.'));
+$section->add($group);
+
+// Conditional text explaining the currently selected filter type
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_none');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('All mDNS names are allowed by default.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_allow');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_deny');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+
+$group = new Form_Group('Global Filter List');
+$group->addClass('sh_global_filter_list');
+$group->add(new Form_Input(
+	'global_filter_list',
+	null,
+	'text',
+	$pconfig['global_filter_list']
+))->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+$section->add($group);
+
+$form->add($section);
+
+// Interface sections
+foreach ($available_interfaces as $interface => $name) {
+	$interface_config = array_get_path($pconfig, "interfaces/{$interface}", []);
+
+	$section = new Form_Section($name . ' Settings');
+	$section->addClass('sh_interface');
+	$section->addClass('sh_interface_' . $interface);
+
+	// IP protocols
+	$group = new Form_Group('IP Protocols');
+	$group->addClass('sh_interface_protocols');
+	$group->add(new Form_Select(
+		'ip_protocols_' . $interface,
+		null,
+		$interface_config['ip_protocols'],
+		$ip_protocol_types
+	))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+	$section->add($group);
+
+	// Inbound filter type
+	$group = new Form_Group('Inbound Filter');
+	$group->add(new Form_Select(
+		'inbound_filter_type_' . $interface,
+		null,
+		$interface_config['inbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The inbound filter is applied to packets received on the interface following the global filter.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected inbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed inbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	$group = new Form_Group('Inbound Filter List');
+	$group->addClass('sh_interface_inbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'inbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'inbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+
+	// Outbound filter type
+	$group = new Form_Group('Outbound Filter');
+	$group->add(new Form_Select(
+		'outbound_filter_type_' . $interface,
+		null,
+		$interface_config['outbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The outbound filter is applied to packets prior to sending packets on the interface.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected outbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed outbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	// Outbound filter list
+	$group = new Form_Group('Outbound Filter List');
+	$group->addClass('sh_interface_outbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'outbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'outbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+	$form->add($section);
+}
+
+// Advanced option to Disable all packet filtering
+$section = new Form_Section('Advanced');
+$section->addClass('sh_disable_packet_filtering');
+$section->addInput(new Form_Checkbox(
+	'disable_packet_filtering',
+	'Disable Packet Filtering',
+	'Completely disable packet decoding and filtering',
+	$pconfig['disable_packet_filtering']
+))->setHelp(gettext('Selecting this option will cause packets received from one interface to be forwarded directly to neighboring interfaces without any further processing. <b>This option disables all mDNS packet validation. Use with caution.</b>'));
+$form->add($section);
+
+print($form);
+?>
+
+
+<script type="text/javascript">
+//<![CDATA[
+events.push(function() {
+
+	// Show/hide interface sections
+	function hideInterfaces() {
+		hideClass('sh_interface', true);
+
+		var selected = $(".active_interfaces").val();
+		var length = $(".active_interfaces :selected").length;
+
+		for (var i=0; i<length; i++) {
+			hideClass('sh_interface_' + selected[i], false);
+		}
+	}
+
+	// Show/hide interface ip protocols based on global ip protocols
+	function hideInterfaceProtocols() {
+		hideClass('sh_interface_protocols', $('#global_ip_protocols').prop('value') != 'both');
+	}
+
+	// Show/hide the advanced
+	function hideAdvanced() {
+<?php
+		// This currently looks at all available interfaces. It might be preferable to only look at selected interfaces.
+		echo "showadvanced = ($('#global_filter_type').prop('value') == 'none')\n";
+		foreach ($available_interfaces as $interface => $name) {
+			echo " && $('#inbound_filter_type_{$interface}').prop('value') == 'none'\n";
+			echo " && $('#outbound_filter_type_{$interface}').prop('value') == 'none'\n";
+		}
+		echo ";\n";
+?>
+		hideClass('sh_disable_packet_filtering', !showadvanced);
+	}
+
+	// Show/hide based on global filter
+	function hideGlobalFilter() {
+		hideClass('sh_global_filter_none', $('#global_filter_type').prop('value') != 'none');
+		hideClass('sh_global_filter_allow', $('#global_filter_type').prop('value') != 'allow');
+		hideClass('sh_global_filter_deny', $('#global_filter_type').prop('value') != 'deny');
+		hideClass('sh_global_filter_list', $('#global_filter_type').prop('value') == 'none');
+	}
+
+<?php
+	// Show/hide based on interface filters
+	foreach ($available_interfaces as $interface => $name) {
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: dennypage
body: If you would like to propose a diff, I'll have a look at it.
created_at: 2025-03-07T18:45:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: marcos-ng
body: WAN interfaces are simply those that have a gateway directly assigned to them (which is implied for those using DHCP for address assignment). If needed, you may check for that specifically. The interface assigned to 'wan' will not always be a WAN-type interface hence why it's incorrect to assume that in code. And yeah, there are a lot of places that still do this - mostly because they haven't been updated in a decade - which tend to cause bugs and generally break configuration that would otherwise be expected to work (this applies to 'lan' as well).
created_at: 2025-03-07T18:53:19Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
+
+if ($_POST) {
+	unset($input_errors);
+	$pconfig = $_POST;
+
+	// Check for Avahi conflict
+	if ($pconfig['enable'] && $avahi_enabled) {
+		$input_errors[] = gettext('Avahi relfection must be disabled before enabling mDNS Bridge');
+	}
+
+	// Validate interfaces
+	if (count($pconfig['active_interfaces']) < 2) {
+		$input_errors[] = gettext('A minimum of two interfaces are required');
+	}
+
+	// Vaidate and normalize the global filter
+	if ($pconfig['global_filter_type'] != 'none' && trim($pconfig['global_filter_list']) == '') {
+		$pconfig['global_filter_type'] = 'none';
+		$pconfig['global_filter_list'] = '';
+	}
+	if ($pconfig['global_filter_type'] != 'none') {
+		$pconfig['disable_packet_filtering'] = false;
+		$filter_list = array();
+		foreach (explode(',', $pconfig['global_filter_list']) as $filter) {
+			$filter = trim($filter);
+			if (!is_domain($filter, false, false)) {
+				$input_errors[] = sprintf(gettext('Invalid domain in Global Filter List: "%1$s"'), $filter);
+			}
+			$filter_list[] = $filter;
+		}
+		$pconfig['global_filter_list'] = implode(', ', $filter_list);
+	}
+
+	// Validate and normalize the interface filters
+	foreach ($pconfig['active_interfaces'] as $interface) {
+		// Inbound filter
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none' && trim($pconfig['inbound_filter_list_' . $interface]) == '') {
+			$pconfig['inbound_filter_type_' . $interface] = 'none';
+			$pconfig['inbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['inbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Inbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['inbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+
+		// Outbound filter
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none' && trim($pconfig['outbound_filter_list_' . $interface]) == '') {
+			$pconfig['outbound_filter_type_' . $interface] = 'none';
+			$pconfig['outbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['outbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Outbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['outbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+	}
+
+	// Rebuild the interfaces array
+	foreach ($available_interfaces as $interface => $name) {
+		array_set_path($pconfig, "interfaces/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+	}
+
+	// Update the config
+	if (!$input_errors) {
+		// Global settings
+		config_set_path($path_enable, $pconfig['enable']);
+		config_set_path($path_carp_vhid, $pconfig['carp_vhid']);
+		config_set_path($path_active_interfaces, implode(',', $pconfig['active_interfaces']));
+		config_set_path($path_global_ip_protocols, $pconfig['global_ip_protocols']);
+		config_set_path($path_global_filter_type, $pconfig['global_filter_type']);
+		config_set_path($path_global_filter_list, $pconfig['global_filter_list']);
+		config_set_path($path_disable_packet_filtering, $pconfig['disable_packet_filtering']);
+
+		// Interface settings
+		foreach ($pconfig['active_interfaces'] as $interface) {
+			config_set_path("{$path_interfaces}/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+		}
+
+		// Write the config
+		write_config(gettext("mDNS Bridge settings changed"));
+
+		// Sync the running configuration
+		mdns_bridge_sync_config();
+	}
+}
+
+
+$ip_protocol_types = array(
+	'both' => gettext('IPv4 and IPv6'),
+	'ipv4' => gettext('IPv4 only'),
+	'ipv6' => gettext('IPv6 only') );
+
+$filter_types = array(
+	'none' => gettext('none'),
+	'allow' => gettext('Allow'),
+	'deny' => gettext('Deny') );
+
+$filter_help_text = gettext(
+	'Comma separated list of mDNS names. Most often, a name should ' .
+	'be a single label representing a service name such as ' .
+	'_printer, _ipp, _ipps, _airplay, _hap, _http or _ssh.');
+$filter_placeholder_text = gettext('name1, name2, name3');
+
+
+$pgtitle = array(gettext("Services"), gettext("mDNS Bridge"));
+include("head.inc");
+
+if ($input_errors) {
+	print_input_errors($input_errors);
+}
+
+
+$form = new Form;
+$section = new Form_Section('General Settings');
+
+// Enable
+$section->addInput(new Form_Checkbox(
+	'enable',
+	'Enable',
+	'Enable the mDNS Bridge daemon',
+	$pconfig['enable']
+));
+
+// CARP
+$section->addInput(new Form_Select(
+	'carp_vhid',
+	'CARP Status VHID',
+	$pconfig['carp_vhid'],
+	mdns_bridge_get_carp_list()
+))->setHelp(gettext('Used for HA MASTER/BACKUP status. mDNS Bridge will be started when the chosen VHID is in MASTER status, and stopped when in BACKUP status.'));
+
+// List of interfaces
+$section->addInput(new Form_Select(
+	'active_interfaces',
+	'*Interfaces',
+	$pconfig['active_interfaces'],
+	$available_interfaces,
+	true
+))->addClass('active_interfaces')->setHelp(gettext('Interfaces that the mDNS Bridge daemon will operate on. Two or more interfaces are required.'));
+$form->add($section);
+
+$section = new Form_Section('Global Settings');
+// Global IP protocol list
+$section->addInput(new Form_Select(
+	'global_ip_protocols',
+	'IP Protocols',
+	$pconfig['global_ip_protocols'],
+	$ip_protocol_types
+))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+
+
+// Global filter type
+$group = new Form_Group('Global Filter');
+$group->add(new Form_Select(
+	'global_filter_type',
+	null,
+	$pconfig['global_filter_type'],
+	$filter_types
+))->setHelp(gettext('The global filter is applied to incoming packets on all interfaces prior to any interface specific filters.'));
+$section->add($group);
+
+// Conditional text explaining the currently selected filter type
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_none');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('All mDNS names are allowed by default.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_allow');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_deny');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+
+$group = new Form_Group('Global Filter List');
+$group->addClass('sh_global_filter_list');
+$group->add(new Form_Input(
+	'global_filter_list',
+	null,
+	'text',
+	$pconfig['global_filter_list']
+))->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+$section->add($group);
+
+$form->add($section);
+
+// Interface sections
+foreach ($available_interfaces as $interface => $name) {
+	$interface_config = array_get_path($pconfig, "interfaces/{$interface}", []);
+
+	$section = new Form_Section($name . ' Settings');
+	$section->addClass('sh_interface');
+	$section->addClass('sh_interface_' . $interface);
+
+	// IP protocols
+	$group = new Form_Group('IP Protocols');
+	$group->addClass('sh_interface_protocols');
+	$group->add(new Form_Select(
+		'ip_protocols_' . $interface,
+		null,
+		$interface_config['ip_protocols'],
+		$ip_protocol_types
+	))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+	$section->add($group);
+
+	// Inbound filter type
+	$group = new Form_Group('Inbound Filter');
+	$group->add(new Form_Select(
+		'inbound_filter_type_' . $interface,
+		null,
+		$interface_config['inbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The inbound filter is applied to packets received on the interface following the global filter.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected inbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed inbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	$group = new Form_Group('Inbound Filter List');
+	$group->addClass('sh_interface_inbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'inbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'inbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+
+	// Outbound filter type
+	$group = new Form_Group('Outbound Filter');
+	$group->add(new Form_Select(
+		'outbound_filter_type_' . $interface,
+		null,
+		$interface_config['outbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The outbound filter is applied to packets prior to sending packets on the interface.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected outbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed outbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	// Outbound filter list
+	$group = new Form_Group('Outbound Filter List');
+	$group->addClass('sh_interface_outbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'outbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'outbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+	$form->add($section);
+}
+
+// Advanced option to Disable all packet filtering
+$section = new Form_Section('Advanced');
+$section->addClass('sh_disable_packet_filtering');
+$section->addInput(new Form_Checkbox(
+	'disable_packet_filtering',
+	'Disable Packet Filtering',
+	'Completely disable packet decoding and filtering',
+	$pconfig['disable_packet_filtering']
+))->setHelp(gettext('Selecting this option will cause packets received from one interface to be forwarded directly to neighboring interfaces without any further processing. <b>This option disables all mDNS packet validation. Use with caution.</b>'));
+$form->add($section);
+
+print($form);
+?>
+
+
+<script type="text/javascript">
+//<![CDATA[
+events.push(function() {
+
+	// Show/hide interface sections
+	function hideInterfaces() {
+		hideClass('sh_interface', true);
+
+		var selected = $(".active_interfaces").val();
+		var length = $(".active_interfaces :selected").length;
+
+		for (var i=0; i<length; i++) {
+			hideClass('sh_interface_' + selected[i], false);
+		}
+	}
+
+	// Show/hide interface ip protocols based on global ip protocols
+	function hideInterfaceProtocols() {
+		hideClass('sh_interface_protocols', $('#global_ip_protocols').prop('value') != 'both');
+	}
+
+	// Show/hide the advanced
+	function hideAdvanced() {
+<?php
+		// This currently looks at all available interfaces. It might be preferable to only look at selected interfaces.
+		echo "showadvanced = ($('#global_filter_type').prop('value') == 'none')\n";
+		foreach ($available_interfaces as $interface => $name) {
+			echo " && $('#inbound_filter_type_{$interface}').prop('value') == 'none'\n";
+			echo " && $('#outbound_filter_type_{$interface}').prop('value') == 'none'\n";
+		}
+		echo ";\n";
+?>
+		hideClass('sh_disable_packet_filtering', !showadvanced);
+	}
+
+	// Show/hide based on global filter
+	function hideGlobalFilter() {
+		hideClass('sh_global_filter_none', $('#global_filter_type').prop('value') != 'none');
+		hideClass('sh_global_filter_allow', $('#global_filter_type').prop('value') != 'allow');
+		hideClass('sh_global_filter_deny', $('#global_filter_type').prop('value') != 'deny');
+		hideClass('sh_global_filter_list', $('#global_filter_type').prop('value') == 'none');
+	}
+
+<?php
+	// Show/hide based on interface filters
+	foreach ($available_interfaces as $interface => $name) {
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: marcos-ng
body: Here's an example for the `hideAdvanced()` function that can be adopted to the rest:
```
var available_interfaces = <?=json_encode(array_keys($available_interfaces))?>;
function hideAdvanced() {
	for (let interface of available_interfaces) {
		showadvanced = (($('#global_filter_type').prop('value') == 'none')
			&& ($('#inbound_filter_type_' + interface).prop('value') == 'none')
			&& ($('#outbound_filter_type_' + interface).prop('value') == 'none')
		);
		hideClass('sh_disable_packet_filtering', !showadvanced);
	}
}
```
created_at: 2025-03-07T19:45:43Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: dennypage
body: To fix 'wan' across the board, we're going to need a function that performs the check. Having each individual package and internal functions perform that check just isn't going to be practical. I'll make the 'wan' change when it comes around. I've already changed 'lan' because it doesn't do any harm.
created_at: 2025-03-07T20:47:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: dennypage
body: Yes, mdns-bridge, like my other packages, explicitly sets the value to 'yes'.
created_at: 2025-03-07T21:00:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
+
+if ($_POST) {
+	unset($input_errors);
+	$pconfig = $_POST;
+
+	// Check for Avahi conflict
+	if ($pconfig['enable'] && $avahi_enabled) {
+		$input_errors[] = gettext('Avahi relfection must be disabled before enabling mDNS Bridge');
+	}
+
+	// Validate interfaces
+	if (count($pconfig['active_interfaces']) < 2) {
+		$input_errors[] = gettext('A minimum of two interfaces are required');
+	}
+
+	// Vaidate and normalize the global filter
+	if ($pconfig['global_filter_type'] != 'none' && trim($pconfig['global_filter_list']) == '') {
+		$pconfig['global_filter_type'] = 'none';
+		$pconfig['global_filter_list'] = '';
+	}
+	if ($pconfig['global_filter_type'] != 'none') {
+		$pconfig['disable_packet_filtering'] = false;
+		$filter_list = array();
+		foreach (explode(',', $pconfig['global_filter_list']) as $filter) {
+			$filter = trim($filter);
+			if (!is_domain($filter, false, false)) {
+				$input_errors[] = sprintf(gettext('Invalid domain in Global Filter List: "%1$s"'), $filter);
+			}
+			$filter_list[] = $filter;
+		}
+		$pconfig['global_filter_list'] = implode(', ', $filter_list);
+	}
+
+	// Validate and normalize the interface filters
+	foreach ($pconfig['active_interfaces'] as $interface) {
+		// Inbound filter
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none' && trim($pconfig['inbound_filter_list_' . $interface]) == '') {
+			$pconfig['inbound_filter_type_' . $interface] = 'none';
+			$pconfig['inbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['inbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Inbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['inbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+
+		// Outbound filter
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none' && trim($pconfig['outbound_filter_list_' . $interface]) == '') {
+			$pconfig['outbound_filter_type_' . $interface] = 'none';
+			$pconfig['outbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['outbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Outbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['outbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+	}
+
+	// Rebuild the interfaces array
+	foreach ($available_interfaces as $interface => $name) {
+		array_set_path($pconfig, "interfaces/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+	}
+
+	// Update the config
+	if (!$input_errors) {
+		// Global settings
+		config_set_path($path_enable, $pconfig['enable']);
+		config_set_path($path_carp_vhid, $pconfig['carp_vhid']);
+		config_set_path($path_active_interfaces, implode(',', $pconfig['active_interfaces']));
+		config_set_path($path_global_ip_protocols, $pconfig['global_ip_protocols']);
+		config_set_path($path_global_filter_type, $pconfig['global_filter_type']);
+		config_set_path($path_global_filter_list, $pconfig['global_filter_list']);
+		config_set_path($path_disable_packet_filtering, $pconfig['disable_packet_filtering']);
+
+		// Interface settings
+		foreach ($pconfig['active_interfaces'] as $interface) {
+			config_set_path("{$path_interfaces}/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+		}
+
+		// Write the config
+		write_config(gettext("mDNS Bridge settings changed"));
+
+		// Sync the running configuration
+		mdns_bridge_sync_config();
+	}
+}
+
+
+$ip_protocol_types = array(
+	'both' => gettext('IPv4 and IPv6'),
+	'ipv4' => gettext('IPv4 only'),
+	'ipv6' => gettext('IPv6 only') );
+
+$filter_types = array(
+	'none' => gettext('none'),
+	'allow' => gettext('Allow'),
+	'deny' => gettext('Deny') );
+
+$filter_help_text = gettext(
+	'Comma separated list of mDNS names. Most often, a name should ' .
+	'be a single label representing a service name such as ' .
+	'_printer, _ipp, _ipps, _airplay, _hap, _http or _ssh.');
+$filter_placeholder_text = gettext('name1, name2, name3');
+
+
+$pgtitle = array(gettext("Services"), gettext("mDNS Bridge"));
+include("head.inc");
+
+if ($input_errors) {
+	print_input_errors($input_errors);
+}
+
+
+$form = new Form;
+$section = new Form_Section('General Settings');
+
+// Enable
+$section->addInput(new Form_Checkbox(
+	'enable',
+	'Enable',
+	'Enable the mDNS Bridge daemon',
+	$pconfig['enable']
+));
+
+// CARP
+$section->addInput(new Form_Select(
+	'carp_vhid',
+	'CARP Status VHID',
+	$pconfig['carp_vhid'],
+	mdns_bridge_get_carp_list()
+))->setHelp(gettext('Used for HA MASTER/BACKUP status. mDNS Bridge will be started when the chosen VHID is in MASTER status, and stopped when in BACKUP status.'));
+
+// List of interfaces
+$section->addInput(new Form_Select(
+	'active_interfaces',
+	'*Interfaces',
+	$pconfig['active_interfaces'],
+	$available_interfaces,
+	true
+))->addClass('active_interfaces')->setHelp(gettext('Interfaces that the mDNS Bridge daemon will operate on. Two or more interfaces are required.'));
+$form->add($section);
+
+$section = new Form_Section('Global Settings');
+// Global IP protocol list
+$section->addInput(new Form_Select(
+	'global_ip_protocols',
+	'IP Protocols',
+	$pconfig['global_ip_protocols'],
+	$ip_protocol_types
+))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+
+
+// Global filter type
+$group = new Form_Group('Global Filter');
+$group->add(new Form_Select(
+	'global_filter_type',
+	null,
+	$pconfig['global_filter_type'],
+	$filter_types
+))->setHelp(gettext('The global filter is applied to incoming packets on all interfaces prior to any interface specific filters.'));
+$section->add($group);
+
+// Conditional text explaining the currently selected filter type
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_none');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('All mDNS names are allowed by default.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_allow');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_deny');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+
+$group = new Form_Group('Global Filter List');
+$group->addClass('sh_global_filter_list');
+$group->add(new Form_Input(
+	'global_filter_list',
+	null,
+	'text',
+	$pconfig['global_filter_list']
+))->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+$section->add($group);
+
+$form->add($section);
+
+// Interface sections
+foreach ($available_interfaces as $interface => $name) {
+	$interface_config = array_get_path($pconfig, "interfaces/{$interface}", []);
+
+	$section = new Form_Section($name . ' Settings');
+	$section->addClass('sh_interface');
+	$section->addClass('sh_interface_' . $interface);
+
+	// IP protocols
+	$group = new Form_Group('IP Protocols');
+	$group->addClass('sh_interface_protocols');
+	$group->add(new Form_Select(
+		'ip_protocols_' . $interface,
+		null,
+		$interface_config['ip_protocols'],
+		$ip_protocol_types
+	))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+	$section->add($group);
+
+	// Inbound filter type
+	$group = new Form_Group('Inbound Filter');
+	$group->add(new Form_Select(
+		'inbound_filter_type_' . $interface,
+		null,
+		$interface_config['inbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The inbound filter is applied to packets received on the interface following the global filter.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected inbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed inbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	$group = new Form_Group('Inbound Filter List');
+	$group->addClass('sh_interface_inbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'inbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'inbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+
+	// Outbound filter type
+	$group = new Form_Group('Outbound Filter');
+	$group->add(new Form_Select(
+		'outbound_filter_type_' . $interface,
+		null,
+		$interface_config['outbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The outbound filter is applied to packets prior to sending packets on the interface.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected outbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed outbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	// Outbound filter list
+	$group = new Form_Group('Outbound Filter List');
+	$group->addClass('sh_interface_outbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'outbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'outbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+	$form->add($section);
+}
+
+// Advanced option to Disable all packet filtering
+$section = new Form_Section('Advanced');
+$section->addClass('sh_disable_packet_filtering');
+$section->addInput(new Form_Checkbox(
+	'disable_packet_filtering',
+	'Disable Packet Filtering',
+	'Completely disable packet decoding and filtering',
+	$pconfig['disable_packet_filtering']
+))->setHelp(gettext('Selecting this option will cause packets received from one interface to be forwarded directly to neighboring interfaces without any further processing. <b>This option disables all mDNS packet validation. Use with caution.</b>'));
+$form->add($section);
+
+print($form);
+?>
+
+
+<script type="text/javascript">
+//<![CDATA[
+events.push(function() {
+
+	// Show/hide interface sections
+	function hideInterfaces() {
+		hideClass('sh_interface', true);
+
+		var selected = $(".active_interfaces").val();
+		var length = $(".active_interfaces :selected").length;
+
+		for (var i=0; i<length; i++) {
+			hideClass('sh_interface_' + selected[i], false);
+		}
+	}
+
+	// Show/hide interface ip protocols based on global ip protocols
+	function hideInterfaceProtocols() {
+		hideClass('sh_interface_protocols', $('#global_ip_protocols').prop('value') != 'both');
+	}
+
+	// Show/hide the advanced
+	function hideAdvanced() {
+<?php
+		// This currently looks at all available interfaces. It might be preferable to only look at selected interfaces.
+		echo "showadvanced = ($('#global_filter_type').prop('value') == 'none')\n";
+		foreach ($available_interfaces as $interface => $name) {
+			echo " && $('#inbound_filter_type_{$interface}').prop('value') == 'none'\n";
+			echo " && $('#outbound_filter_type_{$interface}').prop('value') == 'none'\n";
+		}
+		echo ";\n";
+?>
+		hideClass('sh_disable_packet_filtering', !showadvanced);
+	}
+
+	// Show/hide based on global filter
+	function hideGlobalFilter() {
+		hideClass('sh_global_filter_none', $('#global_filter_type').prop('value') != 'none');
+		hideClass('sh_global_filter_allow', $('#global_filter_type').prop('value') != 'allow');
+		hideClass('sh_global_filter_deny', $('#global_filter_type').prop('value') != 'deny');
+		hideClass('sh_global_filter_list', $('#global_filter_type').prop('value') == 'none');
+	}
+
+<?php
+	// Show/hide based on interface filters
+	foreach ($available_interfaces as $interface => $name) {
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: dennypage
body: I'm not a JS guy, so it'll take me a bit to think that through.
created_at: 2025-03-07T21:02:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: marcos-ng
body: Right. That value comes from the element's `value` which the form class sets to `yes` by default (which differs from the browser-standard 'on'). As part of the ongoing API work, we are considering reworking the 'enable' code. Using the proper function will make that easier, though you may keep it as is since for now it's not strictly required.
created_at: 2025-03-07T21:51:23Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: marcos-ng
body: There are two functions used for this now (that is, to check if it's a "WAN-type" interface). You can do the following:
```
$interface = 'wan';
$interface_is_wan = false;
if (interface_has_gateway($interface) || interface_has_gatewayv6($interface)) {
	$interface_is_wan = true;
}
```
created_at: 2025-03-07T22:34:03Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,258 @@
+<?php
+/*
+ * mdns-bridge.inc
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("config.inc");
+require_once("util.inc");
+require_once("service-utils.inc");
+
+const MDNS_BRIDGE_SVC_NAME = 'mdns-bridge';
+const MDNS_BRIDGE_CMD = '/usr/local/bin/mdns-bridge';
+const MDNS_BRIDGE_RC_FILE = '/usr/local/etc/rc.d/mdns-bridge.sh';
+const MDNS_BRIDGE_CONF_FILE = '/usr/local/etc/mdns-bridge.conf';
+const MDNS_BRIDGE_PID_FILE = '/var/run/mdns-bridge.pid';
+
+
+// Is mDNS Bridge enabled?
+function mdns_bridge_enabled() {
+	return (config_get_path('installedpackages/mdns-bridge/enable', false));
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/pkg/mdns-bridge.inc
user: login: dennypage
body: Thanks
created_at: 2025-03-07T23:02:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: dennypage
body: Okay, I can work with that.
created_at: 2025-03-07T23:11:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
+
+if ($_POST) {
+	unset($input_errors);
+	$pconfig = $_POST;
+
+	// Check for Avahi conflict
+	if ($pconfig['enable'] && $avahi_enabled) {
+		$input_errors[] = gettext('Avahi relfection must be disabled before enabling mDNS Bridge');
+	}
+
+	// Validate interfaces
+	if (count($pconfig['active_interfaces']) < 2) {
+		$input_errors[] = gettext('A minimum of two interfaces are required');
+	}
+
+	// Vaidate and normalize the global filter
+	if ($pconfig['global_filter_type'] != 'none' && trim($pconfig['global_filter_list']) == '') {
+		$pconfig['global_filter_type'] = 'none';
+		$pconfig['global_filter_list'] = '';
+	}
+	if ($pconfig['global_filter_type'] != 'none') {
+		$pconfig['disable_packet_filtering'] = false;
+		$filter_list = array();
+		foreach (explode(',', $pconfig['global_filter_list']) as $filter) {
+			$filter = trim($filter);
+			if (!is_domain($filter, false, false)) {
+				$input_errors[] = sprintf(gettext('Invalid domain in Global Filter List: "%1$s"'), $filter);
+			}
+			$filter_list[] = $filter;
+		}
+		$pconfig['global_filter_list'] = implode(', ', $filter_list);
+	}
+
+	// Validate and normalize the interface filters
+	foreach ($pconfig['active_interfaces'] as $interface) {
+		// Inbound filter
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none' && trim($pconfig['inbound_filter_list_' . $interface]) == '') {
+			$pconfig['inbound_filter_type_' . $interface] = 'none';
+			$pconfig['inbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['inbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Inbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['inbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+
+		// Outbound filter
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none' && trim($pconfig['outbound_filter_list_' . $interface]) == '') {
+			$pconfig['outbound_filter_type_' . $interface] = 'none';
+			$pconfig['outbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['outbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Outbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['outbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+	}
+
+	// Rebuild the interfaces array
+	foreach ($available_interfaces as $interface => $name) {
+		array_set_path($pconfig, "interfaces/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+	}
+
+	// Update the config
+	if (!$input_errors) {
+		// Global settings
+		config_set_path($path_enable, $pconfig['enable']);
+		config_set_path($path_carp_vhid, $pconfig['carp_vhid']);
+		config_set_path($path_active_interfaces, implode(',', $pconfig['active_interfaces']));
+		config_set_path($path_global_ip_protocols, $pconfig['global_ip_protocols']);
+		config_set_path($path_global_filter_type, $pconfig['global_filter_type']);
+		config_set_path($path_global_filter_list, $pconfig['global_filter_list']);
+		config_set_path($path_disable_packet_filtering, $pconfig['disable_packet_filtering']);
+
+		// Interface settings
+		foreach ($pconfig['active_interfaces'] as $interface) {
+			config_set_path("{$path_interfaces}/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+		}
+
+		// Write the config
+		write_config(gettext("mDNS Bridge settings changed"));
+
+		// Sync the running configuration
+		mdns_bridge_sync_config();
+	}
+}
+
+
+$ip_protocol_types = array(
+	'both' => gettext('IPv4 and IPv6'),
+	'ipv4' => gettext('IPv4 only'),
+	'ipv6' => gettext('IPv6 only') );
+
+$filter_types = array(
+	'none' => gettext('none'),
+	'allow' => gettext('Allow'),
+	'deny' => gettext('Deny') );
+
+$filter_help_text = gettext(
+	'Comma separated list of mDNS names. Most often, a name should ' .
+	'be a single label representing a service name such as ' .
+	'_printer, _ipp, _ipps, _airplay, _hap, _http or _ssh.');
+$filter_placeholder_text = gettext('name1, name2, name3');
+
+
+$pgtitle = array(gettext("Services"), gettext("mDNS Bridge"));
+include("head.inc");
+
+if ($input_errors) {
+	print_input_errors($input_errors);
+}
+
+
+$form = new Form;
+$section = new Form_Section('General Settings');
+
+// Enable
+$section->addInput(new Form_Checkbox(
+	'enable',
+	'Enable',
+	'Enable the mDNS Bridge daemon',
+	$pconfig['enable']
+));
+
+// CARP
+$section->addInput(new Form_Select(
+	'carp_vhid',
+	'CARP Status VHID',
+	$pconfig['carp_vhid'],
+	mdns_bridge_get_carp_list()
+))->setHelp(gettext('Used for HA MASTER/BACKUP status. mDNS Bridge will be started when the chosen VHID is in MASTER status, and stopped when in BACKUP status.'));
+
+// List of interfaces
+$section->addInput(new Form_Select(
+	'active_interfaces',
+	'*Interfaces',
+	$pconfig['active_interfaces'],
+	$available_interfaces,
+	true
+))->addClass('active_interfaces')->setHelp(gettext('Interfaces that the mDNS Bridge daemon will operate on. Two or more interfaces are required.'));
+$form->add($section);
+
+$section = new Form_Section('Global Settings');
+// Global IP protocol list
+$section->addInput(new Form_Select(
+	'global_ip_protocols',
+	'IP Protocols',
+	$pconfig['global_ip_protocols'],
+	$ip_protocol_types
+))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+
+
+// Global filter type
+$group = new Form_Group('Global Filter');
+$group->add(new Form_Select(
+	'global_filter_type',
+	null,
+	$pconfig['global_filter_type'],
+	$filter_types
+))->setHelp(gettext('The global filter is applied to incoming packets on all interfaces prior to any interface specific filters.'));
+$section->add($group);
+
+// Conditional text explaining the currently selected filter type
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_none');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('All mDNS names are allowed by default.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_allow');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_deny');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+
+$group = new Form_Group('Global Filter List');
+$group->addClass('sh_global_filter_list');
+$group->add(new Form_Input(
+	'global_filter_list',
+	null,
+	'text',
+	$pconfig['global_filter_list']
+))->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+$section->add($group);
+
+$form->add($section);
+
+// Interface sections
+foreach ($available_interfaces as $interface => $name) {
+	$interface_config = array_get_path($pconfig, "interfaces/{$interface}", []);
+
+	$section = new Form_Section($name . ' Settings');
+	$section->addClass('sh_interface');
+	$section->addClass('sh_interface_' . $interface);
+
+	// IP protocols
+	$group = new Form_Group('IP Protocols');
+	$group->addClass('sh_interface_protocols');
+	$group->add(new Form_Select(
+		'ip_protocols_' . $interface,
+		null,
+		$interface_config['ip_protocols'],
+		$ip_protocol_types
+	))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+	$section->add($group);
+
+	// Inbound filter type
+	$group = new Form_Group('Inbound Filter');
+	$group->add(new Form_Select(
+		'inbound_filter_type_' . $interface,
+		null,
+		$interface_config['inbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The inbound filter is applied to packets received on the interface following the global filter.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected inbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed inbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	$group = new Form_Group('Inbound Filter List');
+	$group->addClass('sh_interface_inbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'inbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'inbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+
+	// Outbound filter type
+	$group = new Form_Group('Outbound Filter');
+	$group->add(new Form_Select(
+		'outbound_filter_type_' . $interface,
+		null,
+		$interface_config['outbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The outbound filter is applied to packets prior to sending packets on the interface.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected outbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed outbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	// Outbound filter list
+	$group = new Form_Group('Outbound Filter List');
+	$group->addClass('sh_interface_outbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'outbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'outbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+	$form->add($section);
+}
+
+// Advanced option to Disable all packet filtering
+$section = new Form_Section('Advanced');
+$section->addClass('sh_disable_packet_filtering');
+$section->addInput(new Form_Checkbox(
+	'disable_packet_filtering',
+	'Disable Packet Filtering',
+	'Completely disable packet decoding and filtering',
+	$pconfig['disable_packet_filtering']
+))->setHelp(gettext('Selecting this option will cause packets received from one interface to be forwarded directly to neighboring interfaces without any further processing. <b>This option disables all mDNS packet validation. Use with caution.</b>'));
+$form->add($section);
+
+print($form);
+?>
+
+
+<script type="text/javascript">
+//<![CDATA[
+events.push(function() {
+
+	// Show/hide interface sections
+	function hideInterfaces() {
+		hideClass('sh_interface', true);
+
+		var selected = $(".active_interfaces").val();
+		var length = $(".active_interfaces :selected").length;
+
+		for (var i=0; i<length; i++) {
+			hideClass('sh_interface_' + selected[i], false);
+		}
+	}
+
+	// Show/hide interface ip protocols based on global ip protocols
+	function hideInterfaceProtocols() {
+		hideClass('sh_interface_protocols', $('#global_ip_protocols').prop('value') != 'both');
+	}
+
+	// Show/hide the advanced
+	function hideAdvanced() {
+<?php
+		// This currently looks at all available interfaces. It might be preferable to only look at selected interfaces.
+		echo "showadvanced = ($('#global_filter_type').prop('value') == 'none')\n";
+		foreach ($available_interfaces as $interface => $name) {
+			echo " && $('#inbound_filter_type_{$interface}').prop('value') == 'none'\n";
+			echo " && $('#outbound_filter_type_{$interface}').prop('value') == 'none'\n";
+		}
+		echo ";\n";
+?>
+		hideClass('sh_disable_packet_filtering', !showadvanced);
+	}
+
+	// Show/hide based on global filter
+	function hideGlobalFilter() {
+		hideClass('sh_global_filter_none', $('#global_filter_type').prop('value') != 'none');
+		hideClass('sh_global_filter_allow', $('#global_filter_type').prop('value') != 'allow');
+		hideClass('sh_global_filter_deny', $('#global_filter_type').prop('value') != 'deny');
+		hideClass('sh_global_filter_list', $('#global_filter_type').prop('value') == 'none');
+	}
+
+<?php
+	// Show/hide based on interface filters
+	foreach ($available_interfaces as $interface => $name) {
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: dennypage
body: The proposed code doesn't quite match what I'm doing. The proposed code produces this:
```
	// Show/hide the advanced
	var available_interfaces = ["lan","opt2","opt3","opt4"];
	function hideAdvanced() {
		for (let interface of available_interfaces) {
			showadvanced = (($('#global_filter_type').prop('value') == 'none')
				&& ($('#inbound_filter_type_' + interface).prop('value') == 'none')
				&& ($('#outbound_filter_type_' + interface).prop('value') == 'none')
			);
			hideClass('sh_disable_packet_filtering', !showadvanced);
		}
	}

```

Which makes the decision based on the global filter and the last interface.

The existing code produces this:
```
	function hideAdvanced() {
		showadvanced = ($('#global_filter_type').prop('value') == 'none')
		 && $('#inbound_filter_type_lan').prop('value') == 'none'
 		 && $('#outbound_filter_type_lan').prop('value') == 'none'
		 && $('#inbound_filter_type_opt2').prop('value') == 'none'
		 && $('#outbound_filter_type_opt2').prop('value') == 'none'
		 && $('#inbound_filter_type_opt3').prop('value') == 'none'
		 && $('#outbound_filter_type_opt3').prop('value') == 'none'
		 && $('#inbound_filter_type_opt4').prop('value') == 'none'
		 && $('#outbound_filter_type_opt4').prop('value') == 'none'
	;

```
Which makes the decision on all of the filters combined.

I think what I would need is something like this:
```
        // Show/hide advanced
        var available_interfaces = <?=json_encode(array_keys($available_interfaces))?>;
        function hideAdvanced() {
                showadvanced = $('#global_filter_type').prop('value') == 'none';
                for (let interface of available_interfaces) {
                        if ($('#inbound_filter_type_' + interface).prop('value') != 'none' ||
                            $('#outbound_filter_type_' + interface).prop('value') != 'none') {
                                showadvanced = false;
                        }
                }
                hideClass('sh_disable_packet_filtering', !showadvanced);
        }

```

Can you look that over and tell me what you think? Thanks.
created_at: 2025-03-09T16:58:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
+
+if ($_POST) {
+	unset($input_errors);
+	$pconfig = $_POST;
+
+	// Check for Avahi conflict
+	if ($pconfig['enable'] && $avahi_enabled) {
+		$input_errors[] = gettext('Avahi relfection must be disabled before enabling mDNS Bridge');
+	}
+
+	// Validate interfaces
+	if (count($pconfig['active_interfaces']) < 2) {
+		$input_errors[] = gettext('A minimum of two interfaces are required');
+	}
+
+	// Vaidate and normalize the global filter
+	if ($pconfig['global_filter_type'] != 'none' && trim($pconfig['global_filter_list']) == '') {
+		$pconfig['global_filter_type'] = 'none';
+		$pconfig['global_filter_list'] = '';
+	}
+	if ($pconfig['global_filter_type'] != 'none') {
+		$pconfig['disable_packet_filtering'] = false;
+		$filter_list = array();
+		foreach (explode(',', $pconfig['global_filter_list']) as $filter) {
+			$filter = trim($filter);
+			if (!is_domain($filter, false, false)) {
+				$input_errors[] = sprintf(gettext('Invalid domain in Global Filter List: "%1$s"'), $filter);
+			}
+			$filter_list[] = $filter;
+		}
+		$pconfig['global_filter_list'] = implode(', ', $filter_list);
+	}
+
+	// Validate and normalize the interface filters
+	foreach ($pconfig['active_interfaces'] as $interface) {
+		// Inbound filter
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none' && trim($pconfig['inbound_filter_list_' . $interface]) == '') {
+			$pconfig['inbound_filter_type_' . $interface] = 'none';
+			$pconfig['inbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['inbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Inbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['inbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+
+		// Outbound filter
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none' && trim($pconfig['outbound_filter_list_' . $interface]) == '') {
+			$pconfig['outbound_filter_type_' . $interface] = 'none';
+			$pconfig['outbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['outbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Outbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['outbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+	}
+
+	// Rebuild the interfaces array
+	foreach ($available_interfaces as $interface => $name) {
+		array_set_path($pconfig, "interfaces/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+	}
+
+	// Update the config
+	if (!$input_errors) {
+		// Global settings
+		config_set_path($path_enable, $pconfig['enable']);
+		config_set_path($path_carp_vhid, $pconfig['carp_vhid']);
+		config_set_path($path_active_interfaces, implode(',', $pconfig['active_interfaces']));
+		config_set_path($path_global_ip_protocols, $pconfig['global_ip_protocols']);
+		config_set_path($path_global_filter_type, $pconfig['global_filter_type']);
+		config_set_path($path_global_filter_list, $pconfig['global_filter_list']);
+		config_set_path($path_disable_packet_filtering, $pconfig['disable_packet_filtering']);
+
+		// Interface settings
+		foreach ($pconfig['active_interfaces'] as $interface) {
+			config_set_path("{$path_interfaces}/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+		}
+
+		// Write the config
+		write_config(gettext("mDNS Bridge settings changed"));
+
+		// Sync the running configuration
+		mdns_bridge_sync_config();
+	}
+}
+
+
+$ip_protocol_types = array(
+	'both' => gettext('IPv4 and IPv6'),
+	'ipv4' => gettext('IPv4 only'),
+	'ipv6' => gettext('IPv6 only') );
+
+$filter_types = array(
+	'none' => gettext('none'),
+	'allow' => gettext('Allow'),
+	'deny' => gettext('Deny') );
+
+$filter_help_text = gettext(
+	'Comma separated list of mDNS names. Most often, a name should ' .
+	'be a single label representing a service name such as ' .
+	'_printer, _ipp, _ipps, _airplay, _hap, _http or _ssh.');
+$filter_placeholder_text = gettext('name1, name2, name3');
+
+
+$pgtitle = array(gettext("Services"), gettext("mDNS Bridge"));
+include("head.inc");
+
+if ($input_errors) {
+	print_input_errors($input_errors);
+}
+
+
+$form = new Form;
+$section = new Form_Section('General Settings');
+
+// Enable
+$section->addInput(new Form_Checkbox(
+	'enable',
+	'Enable',
+	'Enable the mDNS Bridge daemon',
+	$pconfig['enable']
+));
+
+// CARP
+$section->addInput(new Form_Select(
+	'carp_vhid',
+	'CARP Status VHID',
+	$pconfig['carp_vhid'],
+	mdns_bridge_get_carp_list()
+))->setHelp(gettext('Used for HA MASTER/BACKUP status. mDNS Bridge will be started when the chosen VHID is in MASTER status, and stopped when in BACKUP status.'));
+
+// List of interfaces
+$section->addInput(new Form_Select(
+	'active_interfaces',
+	'*Interfaces',
+	$pconfig['active_interfaces'],
+	$available_interfaces,
+	true
+))->addClass('active_interfaces')->setHelp(gettext('Interfaces that the mDNS Bridge daemon will operate on. Two or more interfaces are required.'));
+$form->add($section);
+
+$section = new Form_Section('Global Settings');
+// Global IP protocol list
+$section->addInput(new Form_Select(
+	'global_ip_protocols',
+	'IP Protocols',
+	$pconfig['global_ip_protocols'],
+	$ip_protocol_types
+))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+
+
+// Global filter type
+$group = new Form_Group('Global Filter');
+$group->add(new Form_Select(
+	'global_filter_type',
+	null,
+	$pconfig['global_filter_type'],
+	$filter_types
+))->setHelp(gettext('The global filter is applied to incoming packets on all interfaces prior to any interface specific filters.'));
+$section->add($group);
+
+// Conditional text explaining the currently selected filter type
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_none');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('All mDNS names are allowed by default.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_allow');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_deny');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+
+$group = new Form_Group('Global Filter List');
+$group->addClass('sh_global_filter_list');
+$group->add(new Form_Input(
+	'global_filter_list',
+	null,
+	'text',
+	$pconfig['global_filter_list']
+))->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+$section->add($group);
+
+$form->add($section);
+
+// Interface sections
+foreach ($available_interfaces as $interface => $name) {
+	$interface_config = array_get_path($pconfig, "interfaces/{$interface}", []);
+
+	$section = new Form_Section($name . ' Settings');
+	$section->addClass('sh_interface');
+	$section->addClass('sh_interface_' . $interface);
+
+	// IP protocols
+	$group = new Form_Group('IP Protocols');
+	$group->addClass('sh_interface_protocols');
+	$group->add(new Form_Select(
+		'ip_protocols_' . $interface,
+		null,
+		$interface_config['ip_protocols'],
+		$ip_protocol_types
+	))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+	$section->add($group);
+
+	// Inbound filter type
+	$group = new Form_Group('Inbound Filter');
+	$group->add(new Form_Select(
+		'inbound_filter_type_' . $interface,
+		null,
+		$interface_config['inbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The inbound filter is applied to packets received on the interface following the global filter.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected inbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed inbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	$group = new Form_Group('Inbound Filter List');
+	$group->addClass('sh_interface_inbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'inbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'inbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+
+	// Outbound filter type
+	$group = new Form_Group('Outbound Filter');
+	$group->add(new Form_Select(
+		'outbound_filter_type_' . $interface,
+		null,
+		$interface_config['outbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The outbound filter is applied to packets prior to sending packets on the interface.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected outbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed outbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	// Outbound filter list
+	$group = new Form_Group('Outbound Filter List');
+	$group->addClass('sh_interface_outbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'outbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'outbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+	$form->add($section);
+}
+
+// Advanced option to Disable all packet filtering
+$section = new Form_Section('Advanced');
+$section->addClass('sh_disable_packet_filtering');
+$section->addInput(new Form_Checkbox(
+	'disable_packet_filtering',
+	'Disable Packet Filtering',
+	'Completely disable packet decoding and filtering',
+	$pconfig['disable_packet_filtering']
+))->setHelp(gettext('Selecting this option will cause packets received from one interface to be forwarded directly to neighboring interfaces without any further processing. <b>This option disables all mDNS packet validation. Use with caution.</b>'));
+$form->add($section);
+
+print($form);
+?>
+
+
+<script type="text/javascript">
+//<![CDATA[
+events.push(function() {
+
+	// Show/hide interface sections
+	function hideInterfaces() {
+		hideClass('sh_interface', true);
+
+		var selected = $(".active_interfaces").val();
+		var length = $(".active_interfaces :selected").length;
+
+		for (var i=0; i<length; i++) {
+			hideClass('sh_interface_' + selected[i], false);
+		}
+	}
+
+	// Show/hide interface ip protocols based on global ip protocols
+	function hideInterfaceProtocols() {
+		hideClass('sh_interface_protocols', $('#global_ip_protocols').prop('value') != 'both');
+	}
+
+	// Show/hide the advanced
+	function hideAdvanced() {
+<?php
+		// This currently looks at all available interfaces. It might be preferable to only look at selected interfaces.
+		echo "showadvanced = ($('#global_filter_type').prop('value') == 'none')\n";
+		foreach ($available_interfaces as $interface => $name) {
+			echo " && $('#inbound_filter_type_{$interface}').prop('value') == 'none'\n";
+			echo " && $('#outbound_filter_type_{$interface}').prop('value') == 'none'\n";
+		}
+		echo ";\n";
+?>
+		hideClass('sh_disable_packet_filtering', !showadvanced);
+	}
+
+	// Show/hide based on global filter
+	function hideGlobalFilter() {
+		hideClass('sh_global_filter_none', $('#global_filter_type').prop('value') != 'none');
+		hideClass('sh_global_filter_allow', $('#global_filter_type').prop('value') != 'allow');
+		hideClass('sh_global_filter_deny', $('#global_filter_type').prop('value') != 'deny');
+		hideClass('sh_global_filter_list', $('#global_filter_type').prop('value') == 'none');
+	}
+
+<?php
+	// Show/hide based on interface filters
+	foreach ($available_interfaces as $interface => $name) {
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: dennypage
body: Also, I'd appreciate any examples for the other cases where I'm conditionally defining functions such as this:
```
        foreach ($available_interfaces as $interface => $name) {
                echo "function hideInterfaceInboundFilter_{$interface}() {";
                        echo "hideClass('sh_interface_inbound_filter_none_{$interface}', $('#inbound_filter_type_{$interface}').prop('value') != 'none
');";
                        echo "hideClass('sh_interface_inbound_filter_allow_{$interface}', $('#inbound_filter_type_{$interface}').prop('value') != 'all
ow');";
                        echo "hideClass('sh_interface_inbound_filter_deny_{$interface}', $('#inbound_filter_type_{$interface}').prop('value') != 'deny
');";
                        echo "hideClass('sh_interface_inbound_filter_list_{$interface}', $('#inbound_filter_type_{$interface}').prop('value') == 'none
');";
                echo "}\n";
        }

```

and defining change functions such as this:

```
        foreach ($available_interfaces as $interface => $name) {
                echo "$('#inbound_filter_type_{$interface}').change(function() {";
                        echo "hideInterfaceInboundFilter_{$interface}();";
                        echo "hideAdvanced();";
                echo "});\n";
        }

```

I'm not a JS guy, so how to extrapolate the hideAdvanced function isn't obvious to me. Thanks.
created_at: 2025-03-09T17:19:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
+
+if ($_POST) {
+	unset($input_errors);
+	$pconfig = $_POST;
+
+	// Check for Avahi conflict
+	if ($pconfig['enable'] && $avahi_enabled) {
+		$input_errors[] = gettext('Avahi relfection must be disabled before enabling mDNS Bridge');
+	}
+
+	// Validate interfaces
+	if (count($pconfig['active_interfaces']) < 2) {
+		$input_errors[] = gettext('A minimum of two interfaces are required');
+	}
+
+	// Vaidate and normalize the global filter
+	if ($pconfig['global_filter_type'] != 'none' && trim($pconfig['global_filter_list']) == '') {
+		$pconfig['global_filter_type'] = 'none';
+		$pconfig['global_filter_list'] = '';
+	}
+	if ($pconfig['global_filter_type'] != 'none') {
+		$pconfig['disable_packet_filtering'] = false;
+		$filter_list = array();
+		foreach (explode(',', $pconfig['global_filter_list']) as $filter) {
+			$filter = trim($filter);
+			if (!is_domain($filter, false, false)) {
+				$input_errors[] = sprintf(gettext('Invalid domain in Global Filter List: "%1$s"'), $filter);
+			}
+			$filter_list[] = $filter;
+		}
+		$pconfig['global_filter_list'] = implode(', ', $filter_list);
+	}
+
+	// Validate and normalize the interface filters
+	foreach ($pconfig['active_interfaces'] as $interface) {
+		// Inbound filter
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none' && trim($pconfig['inbound_filter_list_' . $interface]) == '') {
+			$pconfig['inbound_filter_type_' . $interface] = 'none';
+			$pconfig['inbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['inbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Inbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['inbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+
+		// Outbound filter
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none' && trim($pconfig['outbound_filter_list_' . $interface]) == '') {
+			$pconfig['outbound_filter_type_' . $interface] = 'none';
+			$pconfig['outbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['outbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Outbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['outbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+	}
+
+	// Rebuild the interfaces array
+	foreach ($available_interfaces as $interface => $name) {
+		array_set_path($pconfig, "interfaces/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+	}
+
+	// Update the config
+	if (!$input_errors) {
+		// Global settings
+		config_set_path($path_enable, $pconfig['enable']);
+		config_set_path($path_carp_vhid, $pconfig['carp_vhid']);
+		config_set_path($path_active_interfaces, implode(',', $pconfig['active_interfaces']));
+		config_set_path($path_global_ip_protocols, $pconfig['global_ip_protocols']);
+		config_set_path($path_global_filter_type, $pconfig['global_filter_type']);
+		config_set_path($path_global_filter_list, $pconfig['global_filter_list']);
+		config_set_path($path_disable_packet_filtering, $pconfig['disable_packet_filtering']);
+
+		// Interface settings
+		foreach ($pconfig['active_interfaces'] as $interface) {
+			config_set_path("{$path_interfaces}/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+		}
+
+		// Write the config
+		write_config(gettext("mDNS Bridge settings changed"));
+
+		// Sync the running configuration
+		mdns_bridge_sync_config();
+	}
+}
+
+
+$ip_protocol_types = array(
+	'both' => gettext('IPv4 and IPv6'),
+	'ipv4' => gettext('IPv4 only'),
+	'ipv6' => gettext('IPv6 only') );
+
+$filter_types = array(
+	'none' => gettext('none'),
+	'allow' => gettext('Allow'),
+	'deny' => gettext('Deny') );
+
+$filter_help_text = gettext(
+	'Comma separated list of mDNS names. Most often, a name should ' .
+	'be a single label representing a service name such as ' .
+	'_printer, _ipp, _ipps, _airplay, _hap, _http or _ssh.');
+$filter_placeholder_text = gettext('name1, name2, name3');
+
+
+$pgtitle = array(gettext("Services"), gettext("mDNS Bridge"));
+include("head.inc");
+
+if ($input_errors) {
+	print_input_errors($input_errors);
+}
+
+
+$form = new Form;
+$section = new Form_Section('General Settings');
+
+// Enable
+$section->addInput(new Form_Checkbox(
+	'enable',
+	'Enable',
+	'Enable the mDNS Bridge daemon',
+	$pconfig['enable']
+));
+
+// CARP
+$section->addInput(new Form_Select(
+	'carp_vhid',
+	'CARP Status VHID',
+	$pconfig['carp_vhid'],
+	mdns_bridge_get_carp_list()
+))->setHelp(gettext('Used for HA MASTER/BACKUP status. mDNS Bridge will be started when the chosen VHID is in MASTER status, and stopped when in BACKUP status.'));
+
+// List of interfaces
+$section->addInput(new Form_Select(
+	'active_interfaces',
+	'*Interfaces',
+	$pconfig['active_interfaces'],
+	$available_interfaces,
+	true
+))->addClass('active_interfaces')->setHelp(gettext('Interfaces that the mDNS Bridge daemon will operate on. Two or more interfaces are required.'));
+$form->add($section);
+
+$section = new Form_Section('Global Settings');
+// Global IP protocol list
+$section->addInput(new Form_Select(
+	'global_ip_protocols',
+	'IP Protocols',
+	$pconfig['global_ip_protocols'],
+	$ip_protocol_types
+))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+
+
+// Global filter type
+$group = new Form_Group('Global Filter');
+$group->add(new Form_Select(
+	'global_filter_type',
+	null,
+	$pconfig['global_filter_type'],
+	$filter_types
+))->setHelp(gettext('The global filter is applied to incoming packets on all interfaces prior to any interface specific filters.'));
+$section->add($group);
+
+// Conditional text explaining the currently selected filter type
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_none');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('All mDNS names are allowed by default.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_allow');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_deny');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+
+$group = new Form_Group('Global Filter List');
+$group->addClass('sh_global_filter_list');
+$group->add(new Form_Input(
+	'global_filter_list',
+	null,
+	'text',
+	$pconfig['global_filter_list']
+))->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+$section->add($group);
+
+$form->add($section);
+
+// Interface sections
+foreach ($available_interfaces as $interface => $name) {
+	$interface_config = array_get_path($pconfig, "interfaces/{$interface}", []);
+
+	$section = new Form_Section($name . ' Settings');
+	$section->addClass('sh_interface');
+	$section->addClass('sh_interface_' . $interface);
+
+	// IP protocols
+	$group = new Form_Group('IP Protocols');
+	$group->addClass('sh_interface_protocols');
+	$group->add(new Form_Select(
+		'ip_protocols_' . $interface,
+		null,
+		$interface_config['ip_protocols'],
+		$ip_protocol_types
+	))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+	$section->add($group);
+
+	// Inbound filter type
+	$group = new Form_Group('Inbound Filter');
+	$group->add(new Form_Select(
+		'inbound_filter_type_' . $interface,
+		null,
+		$interface_config['inbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The inbound filter is applied to packets received on the interface following the global filter.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected inbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed inbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	$group = new Form_Group('Inbound Filter List');
+	$group->addClass('sh_interface_inbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'inbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'inbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+
+	// Outbound filter type
+	$group = new Form_Group('Outbound Filter');
+	$group->add(new Form_Select(
+		'outbound_filter_type_' . $interface,
+		null,
+		$interface_config['outbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The outbound filter is applied to packets prior to sending packets on the interface.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected outbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed outbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	// Outbound filter list
+	$group = new Form_Group('Outbound Filter List');
+	$group->addClass('sh_interface_outbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'outbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'outbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+	$form->add($section);
+}
+
+// Advanced option to Disable all packet filtering
+$section = new Form_Section('Advanced');
+$section->addClass('sh_disable_packet_filtering');
+$section->addInput(new Form_Checkbox(
+	'disable_packet_filtering',
+	'Disable Packet Filtering',
+	'Completely disable packet decoding and filtering',
+	$pconfig['disable_packet_filtering']
+))->setHelp(gettext('Selecting this option will cause packets received from one interface to be forwarded directly to neighboring interfaces without any further processing. <b>This option disables all mDNS packet validation. Use with caution.</b>'));
+$form->add($section);
+
+print($form);
+?>
+
+
+<script type="text/javascript">
+//<![CDATA[
+events.push(function() {
+
+	// Show/hide interface sections
+	function hideInterfaces() {
+		hideClass('sh_interface', true);
+
+		var selected = $(".active_interfaces").val();
+		var length = $(".active_interfaces :selected").length;
+
+		for (var i=0; i<length; i++) {
+			hideClass('sh_interface_' + selected[i], false);
+		}
+	}
+
+	// Show/hide interface ip protocols based on global ip protocols
+	function hideInterfaceProtocols() {
+		hideClass('sh_interface_protocols', $('#global_ip_protocols').prop('value') != 'both');
+	}
+
+	// Show/hide the advanced
+	function hideAdvanced() {
+<?php
+		// This currently looks at all available interfaces. It might be preferable to only look at selected interfaces.
+		echo "showadvanced = ($('#global_filter_type').prop('value') == 'none')\n";
+		foreach ($available_interfaces as $interface => $name) {
+			echo " && $('#inbound_filter_type_{$interface}').prop('value') == 'none'\n";
+			echo " && $('#outbound_filter_type_{$interface}').prop('value') == 'none'\n";
+		}
+		echo ";\n";
+?>
+		hideClass('sh_disable_packet_filtering', !showadvanced);
+	}
+
+	// Show/hide based on global filter
+	function hideGlobalFilter() {
+		hideClass('sh_global_filter_none', $('#global_filter_type').prop('value') != 'none');
+		hideClass('sh_global_filter_allow', $('#global_filter_type').prop('value') != 'allow');
+		hideClass('sh_global_filter_deny', $('#global_filter_type').prop('value') != 'deny');
+		hideClass('sh_global_filter_list', $('#global_filter_type').prop('value') == 'none');
+	}
+
+<?php
+	// Show/hide based on interface filters
+	foreach ($available_interfaces as $interface => $name) {
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: marcos-ng
body: I can try adding a commit to the branch and you can revise/test as needed.
created_at: 2025-03-10T23:08:16Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
+
+if ($_POST) {
+	unset($input_errors);
+	$pconfig = $_POST;
+
+	// Check for Avahi conflict
+	if ($pconfig['enable'] && $avahi_enabled) {
+		$input_errors[] = gettext('Avahi relfection must be disabled before enabling mDNS Bridge');
+	}
+
+	// Validate interfaces
+	if (count($pconfig['active_interfaces']) < 2) {
+		$input_errors[] = gettext('A minimum of two interfaces are required');
+	}
+
+	// Vaidate and normalize the global filter
+	if ($pconfig['global_filter_type'] != 'none' && trim($pconfig['global_filter_list']) == '') {
+		$pconfig['global_filter_type'] = 'none';
+		$pconfig['global_filter_list'] = '';
+	}
+	if ($pconfig['global_filter_type'] != 'none') {
+		$pconfig['disable_packet_filtering'] = false;
+		$filter_list = array();
+		foreach (explode(',', $pconfig['global_filter_list']) as $filter) {
+			$filter = trim($filter);
+			if (!is_domain($filter, false, false)) {
+				$input_errors[] = sprintf(gettext('Invalid domain in Global Filter List: "%1$s"'), $filter);
+			}
+			$filter_list[] = $filter;
+		}
+		$pconfig['global_filter_list'] = implode(', ', $filter_list);
+	}
+
+	// Validate and normalize the interface filters
+	foreach ($pconfig['active_interfaces'] as $interface) {
+		// Inbound filter
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none' && trim($pconfig['inbound_filter_list_' . $interface]) == '') {
+			$pconfig['inbound_filter_type_' . $interface] = 'none';
+			$pconfig['inbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['inbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Inbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['inbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+
+		// Outbound filter
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none' && trim($pconfig['outbound_filter_list_' . $interface]) == '') {
+			$pconfig['outbound_filter_type_' . $interface] = 'none';
+			$pconfig['outbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['outbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Outbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['outbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+	}
+
+	// Rebuild the interfaces array
+	foreach ($available_interfaces as $interface => $name) {
+		array_set_path($pconfig, "interfaces/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+	}
+
+	// Update the config
+	if (!$input_errors) {
+		// Global settings
+		config_set_path($path_enable, $pconfig['enable']);
+		config_set_path($path_carp_vhid, $pconfig['carp_vhid']);
+		config_set_path($path_active_interfaces, implode(',', $pconfig['active_interfaces']));
+		config_set_path($path_global_ip_protocols, $pconfig['global_ip_protocols']);
+		config_set_path($path_global_filter_type, $pconfig['global_filter_type']);
+		config_set_path($path_global_filter_list, $pconfig['global_filter_list']);
+		config_set_path($path_disable_packet_filtering, $pconfig['disable_packet_filtering']);
+
+		// Interface settings
+		foreach ($pconfig['active_interfaces'] as $interface) {
+			config_set_path("{$path_interfaces}/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+		}
+
+		// Write the config
+		write_config(gettext("mDNS Bridge settings changed"));
+
+		// Sync the running configuration
+		mdns_bridge_sync_config();
+	}
+}
+
+
+$ip_protocol_types = array(
+	'both' => gettext('IPv4 and IPv6'),
+	'ipv4' => gettext('IPv4 only'),
+	'ipv6' => gettext('IPv6 only') );
+
+$filter_types = array(
+	'none' => gettext('none'),
+	'allow' => gettext('Allow'),
+	'deny' => gettext('Deny') );
+
+$filter_help_text = gettext(
+	'Comma separated list of mDNS names. Most often, a name should ' .
+	'be a single label representing a service name such as ' .
+	'_printer, _ipp, _ipps, _airplay, _hap, _http or _ssh.');
+$filter_placeholder_text = gettext('name1, name2, name3');
+
+
+$pgtitle = array(gettext("Services"), gettext("mDNS Bridge"));
+include("head.inc");
+
+if ($input_errors) {
+	print_input_errors($input_errors);
+}
+
+
+$form = new Form;
+$section = new Form_Section('General Settings');
+
+// Enable
+$section->addInput(new Form_Checkbox(
+	'enable',
+	'Enable',
+	'Enable the mDNS Bridge daemon',
+	$pconfig['enable']
+));
+
+// CARP
+$section->addInput(new Form_Select(
+	'carp_vhid',
+	'CARP Status VHID',
+	$pconfig['carp_vhid'],
+	mdns_bridge_get_carp_list()
+))->setHelp(gettext('Used for HA MASTER/BACKUP status. mDNS Bridge will be started when the chosen VHID is in MASTER status, and stopped when in BACKUP status.'));
+
+// List of interfaces
+$section->addInput(new Form_Select(
+	'active_interfaces',
+	'*Interfaces',
+	$pconfig['active_interfaces'],
+	$available_interfaces,
+	true
+))->addClass('active_interfaces')->setHelp(gettext('Interfaces that the mDNS Bridge daemon will operate on. Two or more interfaces are required.'));
+$form->add($section);
+
+$section = new Form_Section('Global Settings');
+// Global IP protocol list
+$section->addInput(new Form_Select(
+	'global_ip_protocols',
+	'IP Protocols',
+	$pconfig['global_ip_protocols'],
+	$ip_protocol_types
+))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+
+
+// Global filter type
+$group = new Form_Group('Global Filter');
+$group->add(new Form_Select(
+	'global_filter_type',
+	null,
+	$pconfig['global_filter_type'],
+	$filter_types
+))->setHelp(gettext('The global filter is applied to incoming packets on all interfaces prior to any interface specific filters.'));
+$section->add($group);
+
+// Conditional text explaining the currently selected filter type
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_none');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('All mDNS names are allowed by default.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_allow');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_deny');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+
+$group = new Form_Group('Global Filter List');
+$group->addClass('sh_global_filter_list');
+$group->add(new Form_Input(
+	'global_filter_list',
+	null,
+	'text',
+	$pconfig['global_filter_list']
+))->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+$section->add($group);
+
+$form->add($section);
+
+// Interface sections
+foreach ($available_interfaces as $interface => $name) {
+	$interface_config = array_get_path($pconfig, "interfaces/{$interface}", []);
+
+	$section = new Form_Section($name . ' Settings');
+	$section->addClass('sh_interface');
+	$section->addClass('sh_interface_' . $interface);
+
+	// IP protocols
+	$group = new Form_Group('IP Protocols');
+	$group->addClass('sh_interface_protocols');
+	$group->add(new Form_Select(
+		'ip_protocols_' . $interface,
+		null,
+		$interface_config['ip_protocols'],
+		$ip_protocol_types
+	))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+	$section->add($group);
+
+	// Inbound filter type
+	$group = new Form_Group('Inbound Filter');
+	$group->add(new Form_Select(
+		'inbound_filter_type_' . $interface,
+		null,
+		$interface_config['inbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The inbound filter is applied to packets received on the interface following the global filter.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected inbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed inbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	$group = new Form_Group('Inbound Filter List');
+	$group->addClass('sh_interface_inbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'inbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'inbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+
+	// Outbound filter type
+	$group = new Form_Group('Outbound Filter');
+	$group->add(new Form_Select(
+		'outbound_filter_type_' . $interface,
+		null,
+		$interface_config['outbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The outbound filter is applied to packets prior to sending packets on the interface.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected outbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed outbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	// Outbound filter list
+	$group = new Form_Group('Outbound Filter List');
+	$group->addClass('sh_interface_outbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'outbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'outbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+	$form->add($section);
+}
+
+// Advanced option to Disable all packet filtering
+$section = new Form_Section('Advanced');
+$section->addClass('sh_disable_packet_filtering');
+$section->addInput(new Form_Checkbox(
+	'disable_packet_filtering',
+	'Disable Packet Filtering',
+	'Completely disable packet decoding and filtering',
+	$pconfig['disable_packet_filtering']
+))->setHelp(gettext('Selecting this option will cause packets received from one interface to be forwarded directly to neighboring interfaces without any further processing. <b>This option disables all mDNS packet validation. Use with caution.</b>'));
+$form->add($section);
+
+print($form);
+?>
+
+
+<script type="text/javascript">
+//<![CDATA[
+events.push(function() {
+
+	// Show/hide interface sections
+	function hideInterfaces() {
+		hideClass('sh_interface', true);
+
+		var selected = $(".active_interfaces").val();
+		var length = $(".active_interfaces :selected").length;
+
+		for (var i=0; i<length; i++) {
+			hideClass('sh_interface_' + selected[i], false);
+		}
+	}
+
+	// Show/hide interface ip protocols based on global ip protocols
+	function hideInterfaceProtocols() {
+		hideClass('sh_interface_protocols', $('#global_ip_protocols').prop('value') != 'both');
+	}
+
+	// Show/hide the advanced
+	function hideAdvanced() {
+<?php
+		// This currently looks at all available interfaces. It might be preferable to only look at selected interfaces.
+		echo "showadvanced = ($('#global_filter_type').prop('value') == 'none')\n";
+		foreach ($available_interfaces as $interface => $name) {
+			echo " && $('#inbound_filter_type_{$interface}').prop('value') == 'none'\n";
+			echo " && $('#outbound_filter_type_{$interface}').prop('value') == 'none'\n";
+		}
+		echo ";\n";
+?>
+		hideClass('sh_disable_packet_filtering', !showadvanced);
+	}
+
+	// Show/hide based on global filter
+	function hideGlobalFilter() {
+		hideClass('sh_global_filter_none', $('#global_filter_type').prop('value') != 'none');
+		hideClass('sh_global_filter_allow', $('#global_filter_type').prop('value') != 'allow');
+		hideClass('sh_global_filter_deny', $('#global_filter_type').prop('value') != 'deny');
+		hideClass('sh_global_filter_list', $('#global_filter_type').prop('value') == 'none');
+	}
+
+<?php
+	// Show/hide based on interface filters
+	foreach ($available_interfaces as $interface => $name) {
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: dennypage
body: Thank you Marcos! I'll have a look.
created_at: 2025-03-11T17:09:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
+
+if ($_POST) {
+	unset($input_errors);
+	$pconfig = $_POST;
+
+	// Check for Avahi conflict
+	if ($pconfig['enable'] && $avahi_enabled) {
+		$input_errors[] = gettext('Avahi relfection must be disabled before enabling mDNS Bridge');
+	}
+
+	// Validate interfaces
+	if (count($pconfig['active_interfaces']) < 2) {
+		$input_errors[] = gettext('A minimum of two interfaces are required');
+	}
+
+	// Vaidate and normalize the global filter
+	if ($pconfig['global_filter_type'] != 'none' && trim($pconfig['global_filter_list']) == '') {
+		$pconfig['global_filter_type'] = 'none';
+		$pconfig['global_filter_list'] = '';
+	}
+	if ($pconfig['global_filter_type'] != 'none') {
+		$pconfig['disable_packet_filtering'] = false;
+		$filter_list = array();
+		foreach (explode(',', $pconfig['global_filter_list']) as $filter) {
+			$filter = trim($filter);
+			if (!is_domain($filter, false, false)) {
+				$input_errors[] = sprintf(gettext('Invalid domain in Global Filter List: "%1$s"'), $filter);
+			}
+			$filter_list[] = $filter;
+		}
+		$pconfig['global_filter_list'] = implode(', ', $filter_list);
+	}
+
+	// Validate and normalize the interface filters
+	foreach ($pconfig['active_interfaces'] as $interface) {
+		// Inbound filter
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none' && trim($pconfig['inbound_filter_list_' . $interface]) == '') {
+			$pconfig['inbound_filter_type_' . $interface] = 'none';
+			$pconfig['inbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['inbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Inbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['inbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+
+		// Outbound filter
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none' && trim($pconfig['outbound_filter_list_' . $interface]) == '') {
+			$pconfig['outbound_filter_type_' . $interface] = 'none';
+			$pconfig['outbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['outbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Outbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['outbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+	}
+
+	// Rebuild the interfaces array
+	foreach ($available_interfaces as $interface => $name) {
+		array_set_path($pconfig, "interfaces/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+	}
+
+	// Update the config
+	if (!$input_errors) {
+		// Global settings
+		config_set_path($path_enable, $pconfig['enable']);
+		config_set_path($path_carp_vhid, $pconfig['carp_vhid']);
+		config_set_path($path_active_interfaces, implode(',', $pconfig['active_interfaces']));
+		config_set_path($path_global_ip_protocols, $pconfig['global_ip_protocols']);
+		config_set_path($path_global_filter_type, $pconfig['global_filter_type']);
+		config_set_path($path_global_filter_list, $pconfig['global_filter_list']);
+		config_set_path($path_disable_packet_filtering, $pconfig['disable_packet_filtering']);
+
+		// Interface settings
+		foreach ($pconfig['active_interfaces'] as $interface) {
+			config_set_path("{$path_interfaces}/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+		}
+
+		// Write the config
+		write_config(gettext("mDNS Bridge settings changed"));
+
+		// Sync the running configuration
+		mdns_bridge_sync_config();
+	}
+}
+
+
+$ip_protocol_types = array(
+	'both' => gettext('IPv4 and IPv6'),
+	'ipv4' => gettext('IPv4 only'),
+	'ipv6' => gettext('IPv6 only') );
+
+$filter_types = array(
+	'none' => gettext('none'),
+	'allow' => gettext('Allow'),
+	'deny' => gettext('Deny') );
+
+$filter_help_text = gettext(
+	'Comma separated list of mDNS names. Most often, a name should ' .
+	'be a single label representing a service name such as ' .
+	'_printer, _ipp, _ipps, _airplay, _hap, _http or _ssh.');
+$filter_placeholder_text = gettext('name1, name2, name3');
+
+
+$pgtitle = array(gettext("Services"), gettext("mDNS Bridge"));
+include("head.inc");
+
+if ($input_errors) {
+	print_input_errors($input_errors);
+}
+
+
+$form = new Form;
+$section = new Form_Section('General Settings');
+
+// Enable
+$section->addInput(new Form_Checkbox(
+	'enable',
+	'Enable',
+	'Enable the mDNS Bridge daemon',
+	$pconfig['enable']
+));
+
+// CARP
+$section->addInput(new Form_Select(
+	'carp_vhid',
+	'CARP Status VHID',
+	$pconfig['carp_vhid'],
+	mdns_bridge_get_carp_list()
+))->setHelp(gettext('Used for HA MASTER/BACKUP status. mDNS Bridge will be started when the chosen VHID is in MASTER status, and stopped when in BACKUP status.'));
+
+// List of interfaces
+$section->addInput(new Form_Select(
+	'active_interfaces',
+	'*Interfaces',
+	$pconfig['active_interfaces'],
+	$available_interfaces,
+	true
+))->addClass('active_interfaces')->setHelp(gettext('Interfaces that the mDNS Bridge daemon will operate on. Two or more interfaces are required.'));
+$form->add($section);
+
+$section = new Form_Section('Global Settings');
+// Global IP protocol list
+$section->addInput(new Form_Select(
+	'global_ip_protocols',
+	'IP Protocols',
+	$pconfig['global_ip_protocols'],
+	$ip_protocol_types
+))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+
+
+// Global filter type
+$group = new Form_Group('Global Filter');
+$group->add(new Form_Select(
+	'global_filter_type',
+	null,
+	$pconfig['global_filter_type'],
+	$filter_types
+))->setHelp(gettext('The global filter is applied to incoming packets on all interfaces prior to any interface specific filters.'));
+$section->add($group);
+
+// Conditional text explaining the currently selected filter type
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_none');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('All mDNS names are allowed by default.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_allow');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_deny');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+
+$group = new Form_Group('Global Filter List');
+$group->addClass('sh_global_filter_list');
+$group->add(new Form_Input(
+	'global_filter_list',
+	null,
+	'text',
+	$pconfig['global_filter_list']
+))->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+$section->add($group);
+
+$form->add($section);
+
+// Interface sections
+foreach ($available_interfaces as $interface => $name) {
+	$interface_config = array_get_path($pconfig, "interfaces/{$interface}", []);
+
+	$section = new Form_Section($name . ' Settings');
+	$section->addClass('sh_interface');
+	$section->addClass('sh_interface_' . $interface);
+
+	// IP protocols
+	$group = new Form_Group('IP Protocols');
+	$group->addClass('sh_interface_protocols');
+	$group->add(new Form_Select(
+		'ip_protocols_' . $interface,
+		null,
+		$interface_config['ip_protocols'],
+		$ip_protocol_types
+	))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+	$section->add($group);
+
+	// Inbound filter type
+	$group = new Form_Group('Inbound Filter');
+	$group->add(new Form_Select(
+		'inbound_filter_type_' . $interface,
+		null,
+		$interface_config['inbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The inbound filter is applied to packets received on the interface following the global filter.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected inbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed inbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	$group = new Form_Group('Inbound Filter List');
+	$group->addClass('sh_interface_inbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'inbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'inbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+
+	// Outbound filter type
+	$group = new Form_Group('Outbound Filter');
+	$group->add(new Form_Select(
+		'outbound_filter_type_' . $interface,
+		null,
+		$interface_config['outbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The outbound filter is applied to packets prior to sending packets on the interface.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected outbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed outbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	// Outbound filter list
+	$group = new Form_Group('Outbound Filter List');
+	$group->addClass('sh_interface_outbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'outbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'outbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+	$form->add($section);
+}
+
+// Advanced option to Disable all packet filtering
+$section = new Form_Section('Advanced');
+$section->addClass('sh_disable_packet_filtering');
+$section->addInput(new Form_Checkbox(
+	'disable_packet_filtering',
+	'Disable Packet Filtering',
+	'Completely disable packet decoding and filtering',
+	$pconfig['disable_packet_filtering']
+))->setHelp(gettext('Selecting this option will cause packets received from one interface to be forwarded directly to neighboring interfaces without any further processing. <b>This option disables all mDNS packet validation. Use with caution.</b>'));
+$form->add($section);
+
+print($form);
+?>
+
+
+<script type="text/javascript">
+//<![CDATA[
+events.push(function() {
+
+	// Show/hide interface sections
+	function hideInterfaces() {
+		hideClass('sh_interface', true);
+
+		var selected = $(".active_interfaces").val();
+		var length = $(".active_interfaces :selected").length;
+
+		for (var i=0; i<length; i++) {
+			hideClass('sh_interface_' + selected[i], false);
+		}
+	}
+
+	// Show/hide interface ip protocols based on global ip protocols
+	function hideInterfaceProtocols() {
+		hideClass('sh_interface_protocols', $('#global_ip_protocols').prop('value') != 'both');
+	}
+
+	// Show/hide the advanced
+	function hideAdvanced() {
+<?php
+		// This currently looks at all available interfaces. It might be preferable to only look at selected interfaces.
+		echo "showadvanced = ($('#global_filter_type').prop('value') == 'none')\n";
+		foreach ($available_interfaces as $interface => $name) {
+			echo " && $('#inbound_filter_type_{$interface}').prop('value') == 'none'\n";
+			echo " && $('#outbound_filter_type_{$interface}').prop('value') == 'none'\n";
+		}
+		echo ";\n";
+?>
+		hideClass('sh_disable_packet_filtering', !showadvanced);
+	}
+
+	// Show/hide based on global filter
+	function hideGlobalFilter() {
+		hideClass('sh_global_filter_none', $('#global_filter_type').prop('value') != 'none');
+		hideClass('sh_global_filter_allow', $('#global_filter_type').prop('value') != 'allow');
+		hideClass('sh_global_filter_deny', $('#global_filter_type').prop('value') != 'deny');
+		hideClass('sh_global_filter_list', $('#global_filter_type').prop('value') == 'none');
+	}
+
+<?php
+	// Show/hide based on interface filters
+	foreach ($available_interfaces as $interface => $name) {
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: dennypage
body: I fixed a minor typo, and changed it so it only looks at selected interfaces for displaying the advanced section. Please review. Thanks.
created_at: 2025-03-11T18:16:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
+
+if ($_POST) {
+	unset($input_errors);
+	$pconfig = $_POST;
+
+	// Check for Avahi conflict
+	if ($pconfig['enable'] && $avahi_enabled) {
+		$input_errors[] = gettext('Avahi relfection must be disabled before enabling mDNS Bridge');
+	}
+
+	// Validate interfaces
+	if (count($pconfig['active_interfaces']) < 2) {
+		$input_errors[] = gettext('A minimum of two interfaces are required');
+	}
+
+	// Vaidate and normalize the global filter
+	if ($pconfig['global_filter_type'] != 'none' && trim($pconfig['global_filter_list']) == '') {
+		$pconfig['global_filter_type'] = 'none';
+		$pconfig['global_filter_list'] = '';
+	}
+	if ($pconfig['global_filter_type'] != 'none') {
+		$pconfig['disable_packet_filtering'] = false;
+		$filter_list = array();
+		foreach (explode(',', $pconfig['global_filter_list']) as $filter) {
+			$filter = trim($filter);
+			if (!is_domain($filter, false, false)) {
+				$input_errors[] = sprintf(gettext('Invalid domain in Global Filter List: "%1$s"'), $filter);
+			}
+			$filter_list[] = $filter;
+		}
+		$pconfig['global_filter_list'] = implode(', ', $filter_list);
+	}
+
+	// Validate and normalize the interface filters
+	foreach ($pconfig['active_interfaces'] as $interface) {
+		// Inbound filter
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none' && trim($pconfig['inbound_filter_list_' . $interface]) == '') {
+			$pconfig['inbound_filter_type_' . $interface] = 'none';
+			$pconfig['inbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['inbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Inbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['inbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+
+		// Outbound filter
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none' && trim($pconfig['outbound_filter_list_' . $interface]) == '') {
+			$pconfig['outbound_filter_type_' . $interface] = 'none';
+			$pconfig['outbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['outbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Outbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['outbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+	}
+
+	// Rebuild the interfaces array
+	foreach ($available_interfaces as $interface => $name) {
+		array_set_path($pconfig, "interfaces/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+	}
+
+	// Update the config
+	if (!$input_errors) {
+		// Global settings
+		config_set_path($path_enable, $pconfig['enable']);
+		config_set_path($path_carp_vhid, $pconfig['carp_vhid']);
+		config_set_path($path_active_interfaces, implode(',', $pconfig['active_interfaces']));
+		config_set_path($path_global_ip_protocols, $pconfig['global_ip_protocols']);
+		config_set_path($path_global_filter_type, $pconfig['global_filter_type']);
+		config_set_path($path_global_filter_list, $pconfig['global_filter_list']);
+		config_set_path($path_disable_packet_filtering, $pconfig['disable_packet_filtering']);
+
+		// Interface settings
+		foreach ($pconfig['active_interfaces'] as $interface) {
+			config_set_path("{$path_interfaces}/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+		}
+
+		// Write the config
+		write_config(gettext("mDNS Bridge settings changed"));
+
+		// Sync the running configuration
+		mdns_bridge_sync_config();
+	}
+}
+
+
+$ip_protocol_types = array(
+	'both' => gettext('IPv4 and IPv6'),
+	'ipv4' => gettext('IPv4 only'),
+	'ipv6' => gettext('IPv6 only') );
+
+$filter_types = array(
+	'none' => gettext('none'),
+	'allow' => gettext('Allow'),
+	'deny' => gettext('Deny') );
+
+$filter_help_text = gettext(
+	'Comma separated list of mDNS names. Most often, a name should ' .
+	'be a single label representing a service name such as ' .
+	'_printer, _ipp, _ipps, _airplay, _hap, _http or _ssh.');
+$filter_placeholder_text = gettext('name1, name2, name3');
+
+
+$pgtitle = array(gettext("Services"), gettext("mDNS Bridge"));
+include("head.inc");
+
+if ($input_errors) {
+	print_input_errors($input_errors);
+}
+
+
+$form = new Form;
+$section = new Form_Section('General Settings');
+
+// Enable
+$section->addInput(new Form_Checkbox(
+	'enable',
+	'Enable',
+	'Enable the mDNS Bridge daemon',
+	$pconfig['enable']
+));
+
+// CARP
+$section->addInput(new Form_Select(
+	'carp_vhid',
+	'CARP Status VHID',
+	$pconfig['carp_vhid'],
+	mdns_bridge_get_carp_list()
+))->setHelp(gettext('Used for HA MASTER/BACKUP status. mDNS Bridge will be started when the chosen VHID is in MASTER status, and stopped when in BACKUP status.'));
+
+// List of interfaces
+$section->addInput(new Form_Select(
+	'active_interfaces',
+	'*Interfaces',
+	$pconfig['active_interfaces'],
+	$available_interfaces,
+	true
+))->addClass('active_interfaces')->setHelp(gettext('Interfaces that the mDNS Bridge daemon will operate on. Two or more interfaces are required.'));
+$form->add($section);
+
+$section = new Form_Section('Global Settings');
+// Global IP protocol list
+$section->addInput(new Form_Select(
+	'global_ip_protocols',
+	'IP Protocols',
+	$pconfig['global_ip_protocols'],
+	$ip_protocol_types
+))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+
+
+// Global filter type
+$group = new Form_Group('Global Filter');
+$group->add(new Form_Select(
+	'global_filter_type',
+	null,
+	$pconfig['global_filter_type'],
+	$filter_types
+))->setHelp(gettext('The global filter is applied to incoming packets on all interfaces prior to any interface specific filters.'));
+$section->add($group);
+
+// Conditional text explaining the currently selected filter type
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_none');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('All mDNS names are allowed by default.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_allow');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_deny');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+
+$group = new Form_Group('Global Filter List');
+$group->addClass('sh_global_filter_list');
+$group->add(new Form_Input(
+	'global_filter_list',
+	null,
+	'text',
+	$pconfig['global_filter_list']
+))->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+$section->add($group);
+
+$form->add($section);
+
+// Interface sections
+foreach ($available_interfaces as $interface => $name) {
+	$interface_config = array_get_path($pconfig, "interfaces/{$interface}", []);
+
+	$section = new Form_Section($name . ' Settings');
+	$section->addClass('sh_interface');
+	$section->addClass('sh_interface_' . $interface);
+
+	// IP protocols
+	$group = new Form_Group('IP Protocols');
+	$group->addClass('sh_interface_protocols');
+	$group->add(new Form_Select(
+		'ip_protocols_' . $interface,
+		null,
+		$interface_config['ip_protocols'],
+		$ip_protocol_types
+	))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+	$section->add($group);
+
+	// Inbound filter type
+	$group = new Form_Group('Inbound Filter');
+	$group->add(new Form_Select(
+		'inbound_filter_type_' . $interface,
+		null,
+		$interface_config['inbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The inbound filter is applied to packets received on the interface following the global filter.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected inbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed inbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	$group = new Form_Group('Inbound Filter List');
+	$group->addClass('sh_interface_inbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'inbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'inbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+
+	// Outbound filter type
+	$group = new Form_Group('Outbound Filter');
+	$group->add(new Form_Select(
+		'outbound_filter_type_' . $interface,
+		null,
+		$interface_config['outbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The outbound filter is applied to packets prior to sending packets on the interface.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected outbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed outbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	// Outbound filter list
+	$group = new Form_Group('Outbound Filter List');
+	$group->addClass('sh_interface_outbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'outbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'outbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+	$form->add($section);
+}
+
+// Advanced option to Disable all packet filtering
+$section = new Form_Section('Advanced');
+$section->addClass('sh_disable_packet_filtering');
+$section->addInput(new Form_Checkbox(
+	'disable_packet_filtering',
+	'Disable Packet Filtering',
+	'Completely disable packet decoding and filtering',
+	$pconfig['disable_packet_filtering']
+))->setHelp(gettext('Selecting this option will cause packets received from one interface to be forwarded directly to neighboring interfaces without any further processing. <b>This option disables all mDNS packet validation. Use with caution.</b>'));
+$form->add($section);
+
+print($form);
+?>
+
+
+<script type="text/javascript">
+//<![CDATA[
+events.push(function() {
+
+	// Show/hide interface sections
+	function hideInterfaces() {
+		hideClass('sh_interface', true);
+
+		var selected = $(".active_interfaces").val();
+		var length = $(".active_interfaces :selected").length;
+
+		for (var i=0; i<length; i++) {
+			hideClass('sh_interface_' + selected[i], false);
+		}
+	}
+
+	// Show/hide interface ip protocols based on global ip protocols
+	function hideInterfaceProtocols() {
+		hideClass('sh_interface_protocols', $('#global_ip_protocols').prop('value') != 'both');
+	}
+
+	// Show/hide the advanced
+	function hideAdvanced() {
+<?php
+		// This currently looks at all available interfaces. It might be preferable to only look at selected interfaces.
+		echo "showadvanced = ($('#global_filter_type').prop('value') == 'none')\n";
+		foreach ($available_interfaces as $interface => $name) {
+			echo " && $('#inbound_filter_type_{$interface}').prop('value') == 'none'\n";
+			echo " && $('#outbound_filter_type_{$interface}').prop('value') == 'none'\n";
+		}
+		echo ";\n";
+?>
+		hideClass('sh_disable_packet_filtering', !showadvanced);
+	}
+
+	// Show/hide based on global filter
+	function hideGlobalFilter() {
+		hideClass('sh_global_filter_none', $('#global_filter_type').prop('value') != 'none');
+		hideClass('sh_global_filter_allow', $('#global_filter_type').prop('value') != 'allow');
+		hideClass('sh_global_filter_deny', $('#global_filter_type').prop('value') != 'deny');
+		hideClass('sh_global_filter_list', $('#global_filter_type').prop('value') == 'none');
+	}
+
+<?php
+	// Show/hide based on interface filters
+	foreach ($available_interfaces as $interface => $name) {
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: marcos-ng
body: LGTM
created_at: 2025-03-11T19:01:47Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,517 @@
+<?php
+/*
+ * mdns-bridge.php
+ *
+ * part of pfSense (https://www.pfsense.org)
+ * Copyright (c) 2024 Denny Page
+ * 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("guiconfig.inc");
+require_once("mdns-bridge.inc");
+
+$shortcut_section = 'mdns-bridge';
+
+// Configuration paths
+$package_path = 'installedpackages/mdns-bridge/';
+$path_enable = $package_path . 'enable';
+$path_carp_vhid = $package_path . 'carp_vhid';
+$path_active_interfaces = $package_path . 'active_interfaces';
+$path_global_ip_protocols = $package_path . 'global_ip_protocols';
+$path_global_filter_type = $package_path . 'global_filter_type';
+$path_global_filter_list = $package_path . 'global_filter_list';
+$path_disable_packet_filtering = $package_path . 'disable_packet_filtering';
+$path_interfaces = $package_path . 'interfaces';
+
+// Get the current configuration
+$pconfig['enable'] = config_get_path($path_enable);
+$pconfig['carp_vhid'] = config_get_path($path_carp_vhid);
+$pconfig['active_interfaces'] = explode(',', config_get_path($path_active_interfaces, 'lan'));
+$pconfig['global_ip_protocols'] = config_get_path($path_global_ip_protocols, 'both');
+$pconfig['global_filter_type'] = config_get_path($path_global_filter_type, 'none');
+$pconfig['global_filter_list'] = config_get_path($path_global_filter_list, '');
+$pconfig['disable_packet_filtering'] = config_get_path($path_disable_packet_filtering, false);
+$pconfig['interfaces'] = config_get_path($path_interfaces, []);
+
+// Avahi conflict
+$avahi_enabled = config_get_path('installedpackages/avahi/config/0/enable', false) &&
+		 config_get_path('installedpackages/avahi/config/0/reflection', false);
+
+$available_interfaces = get_configured_interface_with_descr();
+unset($available_interfaces['wan']);
+
+if ($_POST) {
+	unset($input_errors);
+	$pconfig = $_POST;
+
+	// Check for Avahi conflict
+	if ($pconfig['enable'] && $avahi_enabled) {
+		$input_errors[] = gettext('Avahi relfection must be disabled before enabling mDNS Bridge');
+	}
+
+	// Validate interfaces
+	if (count($pconfig['active_interfaces']) < 2) {
+		$input_errors[] = gettext('A minimum of two interfaces are required');
+	}
+
+	// Vaidate and normalize the global filter
+	if ($pconfig['global_filter_type'] != 'none' && trim($pconfig['global_filter_list']) == '') {
+		$pconfig['global_filter_type'] = 'none';
+		$pconfig['global_filter_list'] = '';
+	}
+	if ($pconfig['global_filter_type'] != 'none') {
+		$pconfig['disable_packet_filtering'] = false;
+		$filter_list = array();
+		foreach (explode(',', $pconfig['global_filter_list']) as $filter) {
+			$filter = trim($filter);
+			if (!is_domain($filter, false, false)) {
+				$input_errors[] = sprintf(gettext('Invalid domain in Global Filter List: "%1$s"'), $filter);
+			}
+			$filter_list[] = $filter;
+		}
+		$pconfig['global_filter_list'] = implode(', ', $filter_list);
+	}
+
+	// Validate and normalize the interface filters
+	foreach ($pconfig['active_interfaces'] as $interface) {
+		// Inbound filter
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none' && trim($pconfig['inbound_filter_list_' . $interface]) == '') {
+			$pconfig['inbound_filter_type_' . $interface] = 'none';
+			$pconfig['inbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['inbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['inbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Inbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['inbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+
+		// Outbound filter
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none' && trim($pconfig['outbound_filter_list_' . $interface]) == '') {
+			$pconfig['outbound_filter_type_' . $interface] = 'none';
+			$pconfig['outbound_filter_list_' . $interface] = '';
+		}
+		if ($pconfig['outbound_filter_type_' . $interface] != 'none') {
+			$pconfig['disable_packet_filtering'] = false;
+			$filter_list = array();
+			foreach (explode(',', $pconfig['outbound_filter_list_' . $interface]) as $filter) {
+				$filter = trim($filter);
+				if (!is_domain($filter, false, false)) {
+					$input_errors[] = sprintf(gettext('Invalid domain in %1$s Outbound Filter List: "%2$s"'),
+						convert_friendly_interface_to_friendly_descr($interface), $filter);
+				}
+				$filter_list[] = $filter;
+			}
+			$pconfig['outbound_filter_list_' . $interface] = implode(', ', $filter_list);
+		}
+	}
+
+	// Rebuild the interfaces array
+	foreach ($available_interfaces as $interface => $name) {
+		array_set_path($pconfig, "interfaces/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+		array_set_path($pconfig, "interfaces/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+	}
+
+	// Update the config
+	if (!$input_errors) {
+		// Global settings
+		config_set_path($path_enable, $pconfig['enable']);
+		config_set_path($path_carp_vhid, $pconfig['carp_vhid']);
+		config_set_path($path_active_interfaces, implode(',', $pconfig['active_interfaces']));
+		config_set_path($path_global_ip_protocols, $pconfig['global_ip_protocols']);
+		config_set_path($path_global_filter_type, $pconfig['global_filter_type']);
+		config_set_path($path_global_filter_list, $pconfig['global_filter_list']);
+		config_set_path($path_disable_packet_filtering, $pconfig['disable_packet_filtering']);
+
+		// Interface settings
+		foreach ($pconfig['active_interfaces'] as $interface) {
+			config_set_path("{$path_interfaces}/{$interface}/ip_protocols", $pconfig['ip_protocols_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_type", $pconfig['inbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/inbound_filter_list", $pconfig['inbound_filter_list_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_type", $pconfig['outbound_filter_type_' . $interface]);
+			config_set_path("{$path_interfaces}/{$interface}/outbound_filter_list", $pconfig['outbound_filter_list_' . $interface]);
+		}
+
+		// Write the config
+		write_config(gettext("mDNS Bridge settings changed"));
+
+		// Sync the running configuration
+		mdns_bridge_sync_config();
+	}
+}
+
+
+$ip_protocol_types = array(
+	'both' => gettext('IPv4 and IPv6'),
+	'ipv4' => gettext('IPv4 only'),
+	'ipv6' => gettext('IPv6 only') );
+
+$filter_types = array(
+	'none' => gettext('none'),
+	'allow' => gettext('Allow'),
+	'deny' => gettext('Deny') );
+
+$filter_help_text = gettext(
+	'Comma separated list of mDNS names. Most often, a name should ' .
+	'be a single label representing a service name such as ' .
+	'_printer, _ipp, _ipps, _airplay, _hap, _http or _ssh.');
+$filter_placeholder_text = gettext('name1, name2, name3');
+
+
+$pgtitle = array(gettext("Services"), gettext("mDNS Bridge"));
+include("head.inc");
+
+if ($input_errors) {
+	print_input_errors($input_errors);
+}
+
+
+$form = new Form;
+$section = new Form_Section('General Settings');
+
+// Enable
+$section->addInput(new Form_Checkbox(
+	'enable',
+	'Enable',
+	'Enable the mDNS Bridge daemon',
+	$pconfig['enable']
+));
+
+// CARP
+$section->addInput(new Form_Select(
+	'carp_vhid',
+	'CARP Status VHID',
+	$pconfig['carp_vhid'],
+	mdns_bridge_get_carp_list()
+))->setHelp(gettext('Used for HA MASTER/BACKUP status. mDNS Bridge will be started when the chosen VHID is in MASTER status, and stopped when in BACKUP status.'));
+
+// List of interfaces
+$section->addInput(new Form_Select(
+	'active_interfaces',
+	'*Interfaces',
+	$pconfig['active_interfaces'],
+	$available_interfaces,
+	true
+))->addClass('active_interfaces')->setHelp(gettext('Interfaces that the mDNS Bridge daemon will operate on. Two or more interfaces are required.'));
+$form->add($section);
+
+$section = new Form_Section('Global Settings');
+// Global IP protocol list
+$section->addInput(new Form_Select(
+	'global_ip_protocols',
+	'IP Protocols',
+	$pconfig['global_ip_protocols'],
+	$ip_protocol_types
+))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+
+
+// Global filter type
+$group = new Form_Group('Global Filter');
+$group->add(new Form_Select(
+	'global_filter_type',
+	null,
+	$pconfig['global_filter_type'],
+	$filter_types
+))->setHelp(gettext('The global filter is applied to incoming packets on all interfaces prior to any interface specific filters.'));
+$section->add($group);
+
+// Conditional text explaining the currently selected filter type
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_none');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('All mDNS names are allowed by default.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_allow');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+$group = new Form_Group(null);
+$group->addClass('sh_global_filter_deny');
+$group->add(new Form_StaticText(
+	null,
+	sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+		'Global Filter List will be dropped from packets received ' .
+		'on all interfaces.') . '</b>')));
+$section->add($group);
+
+$group = new Form_Group('Global Filter List');
+$group->addClass('sh_global_filter_list');
+$group->add(new Form_Input(
+	'global_filter_list',
+	null,
+	'text',
+	$pconfig['global_filter_list']
+))->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+$section->add($group);
+
+$form->add($section);
+
+// Interface sections
+foreach ($available_interfaces as $interface => $name) {
+	$interface_config = array_get_path($pconfig, "interfaces/{$interface}", []);
+
+	$section = new Form_Section($name . ' Settings');
+	$section->addClass('sh_interface');
+	$section->addClass('sh_interface_' . $interface);
+
+	// IP protocols
+	$group = new Form_Group('IP Protocols');
+	$group->addClass('sh_interface_protocols');
+	$group->add(new Form_Select(
+		'ip_protocols_' . $interface,
+		null,
+		$interface_config['ip_protocols'],
+		$ip_protocol_types
+	))->setHelp(gettext('Select which IP protocols mDNS Bridge will operate on.'));
+	$section->add($group);
+
+	// Inbound filter type
+	$group = new Form_Group('Inbound Filter');
+	$group->add(new Form_Select(
+		'inbound_filter_type_' . $interface,
+		null,
+		$interface_config['inbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The inbound filter is applied to packets received on the interface following the global filter.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected inbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed inbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_inbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Inbound Filter List will be dropped from packets received on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	$group = new Form_Group('Inbound Filter List');
+	$group->addClass('sh_interface_inbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'inbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'inbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+
+	// Outbound filter type
+	$group = new Form_Group('Outbound Filter');
+	$group->add(new Form_Select(
+		'outbound_filter_type_' . $interface,
+		null,
+		$interface_config['outbound_filter_type'],
+		$filter_types
+	))->setHelp(gettext('The outbound filter is applied to packets prior to sending packets on the interface.'));
+	$section->add($group);
+
+	// Conditional text explaining the currently selected outbound filter type
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_none_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('All mDNS names are allowed outbound on the interface.') .
+		'</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_allow_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that do not match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+	$group = new Form_Group(null);
+	$group->addClass('sh_interface_outbound_filter_deny_' . $interface);
+	$group->add(new Form_StaticText(
+		null,
+		sprintf('<b>' . gettext('mDNS names that match an entry in the ' .
+			'Outbound Filter List will be excluded from packets sent on the ' .
+			'interface.') . '</b>')));
+	$section->add($group);
+
+	// Outbound filter list
+	$group = new Form_Group('Outbound Filter List');
+	$group->addClass('sh_interface_outbound_filter_list_' . $interface);
+	$group->add(new Form_Input(
+		'outbound_filter_list_' . $interface,
+		null,
+		'text',
+		array_get_path($interface_config, 'outbound_filter_list',''))
+	)->setHelp($filter_help_text)->setWidth(7)->setAttribute('placeholder', $filter_placeholder_text);
+	$section->add($group);
+
+	$form->add($section);
+}
+
+// Advanced option to Disable all packet filtering
+$section = new Form_Section('Advanced');
+$section->addClass('sh_disable_packet_filtering');
+$section->addInput(new Form_Checkbox(
+	'disable_packet_filtering',
+	'Disable Packet Filtering',
+	'Completely disable packet decoding and filtering',
+	$pconfig['disable_packet_filtering']
+))->setHelp(gettext('Selecting this option will cause packets received from one interface to be forwarded directly to neighboring interfaces without any further processing. <b>This option disables all mDNS packet validation. Use with caution.</b>'));
+$form->add($section);
+
+print($form);
+?>
+
+
+<script type="text/javascript">
+//<![CDATA[
+events.push(function() {
+
+	// Show/hide interface sections
+	function hideInterfaces() {
+		hideClass('sh_interface', true);
+
+		var selected = $(".active_interfaces").val();
+		var length = $(".active_interfaces :selected").length;
+
+		for (var i=0; i<length; i++) {
+			hideClass('sh_interface_' + selected[i], false);
+		}
+	}
+
+	// Show/hide interface ip protocols based on global ip protocols
+	function hideInterfaceProtocols() {
+		hideClass('sh_interface_protocols', $('#global_ip_protocols').prop('value') != 'both');
+	}
+
+	// Show/hide the advanced
+	function hideAdvanced() {
+<?php
+		// This currently looks at all available interfaces. It might be preferable to only look at selected interfaces.
+		echo "showadvanced = ($('#global_filter_type').prop('value') == 'none')\n";
+		foreach ($available_interfaces as $interface => $name) {
+			echo " && $('#inbound_filter_type_{$interface}').prop('value') == 'none'\n";
+			echo " && $('#outbound_filter_type_{$interface}').prop('value') == 'none'\n";
+		}
+		echo ";\n";
+?>
+		hideClass('sh_disable_packet_filtering', !showadvanced);
+	}
+
+	// Show/hide based on global filter
+	function hideGlobalFilter() {
+		hideClass('sh_global_filter_none', $('#global_filter_type').prop('value') != 'none');
+		hideClass('sh_global_filter_allow', $('#global_filter_type').prop('value') != 'allow');
+		hideClass('sh_global_filter_deny', $('#global_filter_type').prop('value') != 'deny');
+		hideClass('sh_global_filter_list', $('#global_filter_type').prop('value') == 'none');
+	}
+
+<?php
+	// Show/hide based on interface filters
+	foreach ($available_interfaces as $interface => $name) {
path: net/pfSense-pkg-mDNS-Bridge/files/usr/local/www/mdns-bridge.php
user: login: dennypage
body: Thanks for the education on JS--much appreciated.
created_at: 2025-03-11T20:55:00Z
author_association: CONTRIBUTOR

PR comments:
user: login: dennypage
created_at: 2025-02-10T18:31:59Z
body: Hey all, can we get this out for testing with 25.03 please? Thanks
author_association: CONTRIBUTOR
user: login: marcos-ng
created_at: 2025-03-06T20:26:14Z
body: For easier review, please submit any changes as additional commits rather than force-pushing the branch. Once the PR is ready, the commits can be squashed before merging.
author_association: COLLABORATOR
user: login: dennypage
created_at: 2025-03-07T17:31:40Z
body: > For easier review, please submit any changes as additional commits rather than force-pushing the branch.

I started down this path, but it's difficult to keep track of where things are. I'm currently doing individual commits so I can keep track of everything, but I can resubmit as a single commit at the end if that is necessary.
author_association: CONTRIBUTOR
user: login: dennypage
created_at: 2025-03-07T19:29:43Z
body: Btw, I did not implement the logging suggestion. Since mdns-bridge in normal operation does not produce log entries beyond start/stop, it didn't seem appropriate to create a log file for its exclusive use.
author_association: CONTRIBUTOR
user: login: marcos-ng
created_at: 2025-03-07T19:57:50Z
body: It's not a requirement but I'd like to see the logging implemented because it helps keep the logs more organized and reduces clutter in the system log. It's easy to do and will make things generally easier to update/maintain (we are considering reworking how logging is done in general).
author_association: COLLABORATOR
user: login: dennypage
created_at: 2025-03-07T20:50:59Z
body: The array change you proposed was good. I like it for efficiency. It's done. Thanks.
author_association: CONTRIBUTOR
user: login: dennypage
created_at: 2025-03-07T23:27:14Z
body: > It's not a requirement but I'd like to see the logging implemented because it helps keep the logs more organized and reduces clutter in the system log. It's easy to do and will make things generally easier to update/maintain (we are considering reworking how logging is done in general).

While it might cut down on the system log itself, overall it makes the logging less useful and will likely make things harder for package maintainers. In supporting the NUT package, I frequently ask people for entries in the system log that contain 'usb' or 'ups', and having to send people to search multiple log files and then stitch together the result will be painful and error prone. There is a similar situation with package restart.

Unless a subsystem or package is generating a lot of entries of its own, I don't think the logs should be separated.
author_association: CONTRIBUTOR
user: login: marcos-ng
created_at: 2025-03-11T19:11:58Z
body: I think it's good. Squash the commits and include the redmine ID in the message - e.g. `net/pfSense-pkg-mDNS-Bridge: Add mDNS Bridge package. Implement #15853`.

The packages will also need to be added here when this is merged:
https://github.com/pfsense/pfsense/blob/master/tools/conf/pfPorts/poudriere_bulk
author_association: COLLABORATOR
user: login: dennypage
created_at: 2025-03-11T20:55:59Z
body: > I think it's good. Squash the commits and include the redmine ID in the message - e.g. `net/pfSense-pkg-mDNS-Bridge: Add mDNS Bridge package. Implement #15853`.

Done.

> The packages will also need to be added here when this is merged: https://github.com/pfsense/pfsense/blob/master/tools/conf/pfPorts/poudriere_bulk

Do you want me to do a PR for this?
author_association: CONTRIBUTOR
user: login: dennypage
created_at: 2025-03-11T21:03:50Z
body: Btw, please don't forget about the related PR:
https://github.com/pfsense/FreeBSD-ports/pull/1392
author_association: CONTRIBUTOR
user: login: marcos-ng
created_at: 2025-03-12T17:17:26Z
body: This is now in dev. After it's been tested working there, it can be picked for 2.8/25.03.
author_association: COLLABORATOR
user: login: dennypage
created_at: 2025-03-12T18:07:22Z
body: Thank you Marcos
author_association: CONTRIBUTOR