diff --git a/www/caddy/pkg-descr b/www/caddy/pkg-descr
index 01962ce7a..d2c94173f 100644
--- a/www/caddy/pkg-descr
+++ b/www/caddy/pkg-descr
@@ -16,6 +16,10 @@ Plugin Changelog
1.7.2
* Add: Directive in HTTP Handler can be chosen, "reverse_proxy" and "redir"
+* Add: Layer4 routes feature. Routing Type "global" or "listener_wrapper" can be chosen
+* Add: Any Layer4 TCP/UDP traffic can be proxied without choosing a Layer 7 protocol matcher
+* Change: Layer4 routes are not ordered automatically anymore
+* Change: Layer4 "not tls sni" matcher has been replaced by generalized "Invert Matchers" checkbox
* Build: Update Caddy Layer4 module, fixes TLS matcher in chromium based browsers
* Fix: When Apply takes longer than 20 seconds, Caddy will be forcefully restarted
diff --git a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogLayer4.xml b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogLayer4.xml
index acac47c8c..a2c73f6a0 100644
--- a/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogLayer4.xml
+++ b/www/caddy/src/opnsense/mvc/app/controllers/OPNsense/Caddy/forms/dialogLayer4.xml
@@ -5,6 +5,12 @@
checkbox
+
+ layer4.Sequence
+
+ text
+
+
layer4.description
@@ -13,21 +19,59 @@
header
-
+
+ true
- layer4.FromDomain
-
- select_multiple
-
- true
-
+ layer4.Type
+
+ dropdown
+
+ true
+
+
+ header
+
+ true
+
+
+ layer4.Protocol
+
+ dropdown
+
+ true
+
+
+ layer4.FromPort
+
+ text
+
+ true
+
+
+ header
+
layer4.Matchers
dropdown
-
+
+
+
+ layer4.FromDomain
+
+ select_multiple
+
+ true
+
+
+
+ layer4.InvertMatchers
+
+ checkbox
+
+ true
header
@@ -64,7 +108,6 @@
header
- true
layer4.RemoteIp
diff --git a/www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/Caddy.php b/www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/Caddy.php
index ddb6f919f..aefc67b9b 100644
--- a/www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/Caddy.php
+++ b/www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/Caddy.php
@@ -37,11 +37,11 @@ use OPNsense\Core\Config;
class Caddy extends BaseModel
{
// Check domain-port combinations
- private function checkForUniquePortCombos($items, $messages)
+ private function checkForUniquePortCombos($messages)
{
$combos = [];
- foreach ($items as $item) {
- $key = $item->__reference; // Dynamic key based on item reference
+ foreach ($this->reverseproxy->reverse->iterateItems() as $item) {
+ $key = $item->__reference;
$fromDomain = (string) $item->FromDomain;
$fromPort = (string) $item->FromPort;
@@ -52,12 +52,9 @@ class Caddy extends BaseModel
}
foreach ($defaultPorts as $port) {
- // Create a unique key for domain-port combination
$comboKey = $fromDomain . ':' . $port;
- // Check for duplicate combinations
if (isset($combos[$comboKey])) {
- // Use dynamic $key for message referencing
$messages->appendMessage(new Message(
sprintf(
gettext(
@@ -76,48 +73,6 @@ class Caddy extends BaseModel
}
}
- // Check that subdomains are under a wildcard or exact domain
- private function checkSubdomainsAgainstDomains($subdomains, $domains, $messages)
- {
- $wildcardDomainList = [];
- foreach ($domains as $domain) {
- if ((string) $domain->enabled === '1') {
- $domainName = (string) $domain->FromDomain;
- if (str_starts_with($domainName, '*.')) {
- $wildcardBase = substr($domainName, 2);
- $wildcardDomainList[$wildcardBase] = $domainName;
- }
- }
- }
-
- foreach ($subdomains as $subdomain) {
- if ((string) $subdomain->enabled === '1') {
- $subdomainName = (string) $subdomain->FromDomain;
- $isValid = false;
- foreach ($wildcardDomainList as $baseDomain => $wildcardDomain) {
- if (str_ends_with($subdomainName, $baseDomain)) {
- $isValid = true;
- break;
- }
- }
-
- if (!$isValid) {
- $key = $subdomain->__reference; // Dynamic key based on subdomain reference
- $messages->appendMessage(new Message(
- sprintf(
- gettext(
- 'Invalid subdomain configuration: %s does not fall ' .
- 'under any configured wildcard domain.'
- ),
- $subdomainName
- ),
- $key . ".FromDomain"
- ));
- }
- }
- }
- }
-
// Get the current OPNsense WebGUI ports and check for conflicts with Caddy
private function getWebGuiPorts()
{
@@ -172,8 +127,7 @@ class Caddy extends BaseModel
private function checkDisableTlsConflicts($messages)
{
foreach ($this->reverseproxy->reverse->iterateItems() as $item) {
- // First check if the DisableTls field has been changed
- if ($item->isFieldChanged('DisableTls')) {
+ if ($item->isFieldChanged()) {
if ((string) $item->DisableTls === '1') {
$conflictChecks = [
'DnsChallenge' => (string) $item->DnsChallenge === '1',
@@ -214,7 +168,7 @@ class Caddy extends BaseModel
if ($httpPort < 1024) {
$messages->appendMessage(new Message(
gettext(
- 'Superuser is disabled, HTTP port must not be empty and must be 1024 or above.'
+ 'www user is active, HTTP port must not be empty and must be 1024 or above.'
),
"general.HttpPort"
));
@@ -224,7 +178,7 @@ class Caddy extends BaseModel
if ($httpsPort < 1024) {
$messages->appendMessage(new Message(
gettext(
- 'Superuser is disabled, HTTPS port must not be empty and must be 1024 or above.'
+ 'www user is active, HTTPS port must not be empty and must be 1024 or above.'
),
"general.HttpsPort"
));
@@ -237,7 +191,7 @@ class Caddy extends BaseModel
if ($fromPort !== null && $fromPort < 1024) {
$messages->appendMessage(new Message(
gettext(
- 'Superuser is disabled, port must be empty or must be 1024 or above.'
+ 'www user is active, port must be empty or must be 1024 or above.'
),
$item->__reference . ".FromPort"
));
@@ -249,36 +203,111 @@ class Caddy extends BaseModel
));
}
}
+
+ foreach ($this->reverseproxy->layer4->iterateItems() as $item) {
+ $fromPort = !empty((string)$item->FromPort) ? (string)$item->FromPort : null;
+
+ if ($fromPort !== null && $fromPort < 1024) {
+ $messages->appendMessage(new Message(
+ gettext(
+ 'www user is active, port must be empty or must be 1024 or above.'
+ ),
+ $item->__reference . ".FromPort"
+ ));
+ $messages->appendMessage(new Message(
+ gettext(
+ 'Ports in "Reverse Proxy - Layer4 Routes" must be empty or must be 1024 or above.'
+ ),
+ "general.DisableSuperuser"
+ ));
+ }
+ }
}
}
- /**
- * Check that when certain Layer4 matchers are selected, only "*" is valid as FromDomain.
- * This happens because they cannot be matched by host header or SNI, so they match all traffic.
- * The "*" shows the user that all traffic will be matched, and that creating multiple
- * matchers will not result in more routes for the same traffic type to work.
- */
private function checkLayer4Matchers($messages)
{
foreach ($this->reverseproxy->layer4->iterateItems() as $item) {
- $matchers = (string) $item->Matchers;
- $fromDomain = (string) $item->FromDomain;
-
- // Check if matchers is not in the list of specific values
- $isNotInSpecificMatchers = !in_array($matchers, ['httphost', 'tlssni', 'nottlssni']);
- $isInvalidFromDomain = $fromDomain !== '*';
-
- if ($isNotInSpecificMatchers && $isInvalidFromDomain) {
+ if ($item->isFieldChanged()) {
$key = $item->__reference;
- $messages->appendMessage(new Message(
- sprintf(
- gettext(
- 'When "%s" matcher is selected, the only valid entry in Domain is "*".'
+ if (in_array((string)$item->Matchers, ['httphost', 'tlssni']) && empty((string)$item->FromDomain)) {
+ $messages->appendMessage(new Message(
+ sprintf(
+ gettext(
+ 'When "%s" matcher is selected, domain is required.'
+ ),
+ $item->Matchers
),
- $matchers
- ),
- $key . ".FromDomain"
- ));
+ $key . ".FromDomain"
+ ));
+ } elseif (
+ !in_array((string)$item->Matchers, ['httphost', 'tlssni']) &&
+ (
+ !empty((string)$item->FromDomain) &&
+ (string)$item->FromDomain != '*'
+ )
+ ) {
+ $messages->appendMessage(new Message(
+ sprintf(
+ gettext(
+ 'When "%s" matcher is selected, domain must be empty or *.'
+ ),
+ $item->Matchers
+ ),
+ $key . ".FromDomain"
+ ));
+ }
+
+ if ((string)$item->Type === 'global' && empty((string)$item->FromPort)) {
+ $messages->appendMessage(new Message(
+ sprintf(
+ gettext(
+ 'When routing type is "%s", port is required.'
+ ),
+ $item->Type
+ ),
+ $key . ".FromPort"
+ ));
+ } elseif ((string)$item->Type !== 'global' && !empty((string)$item->FromPort)) {
+ $messages->appendMessage(new Message(
+ sprintf(
+ gettext(
+ 'When routing type is "%s", port must be empty.'
+ ),
+ $item->Type
+ ),
+ $key . ".FromPort"
+ ));
+ }
+
+ if ((string)$item->Type !== 'global' && ((string)$item->Protocol !== 'tcp')) {
+ $messages->appendMessage(new Message(
+ sprintf(
+ gettext(
+ 'When routing type is "%s", protocol must be TCP.'
+ ),
+ $item->Type
+ ),
+ $key . ".Protocol"
+ ));
+ }
+
+ if ((string)$item->Type !== 'global' &&
+ (
+ (string)$item->Matchers == 'tls' ||
+ (string)$item->Matchers == 'http'
+ )
+ ) {
+ $messages->appendMessage(new Message(
+ sprintf(
+ gettext(
+ 'When routing type is "%s", matchers "HTTP" or "TLS" cannot be chosen.'
+ ),
+ $item->Type
+ ),
+ $key . ".Matchers"
+ ));
+ }
}
}
}
@@ -288,29 +317,10 @@ class Caddy extends BaseModel
{
$messages = parent::performValidation($validateFullModel);
- // Check domain-port combinations
- $this->checkForUniquePortCombos(
- $this->reverseproxy->reverse->iterateItems(),
- $messages
- );
-
- // Check that subdomains are under a wildcard or exact domain
- $this->checkSubdomainsAgainstDomains(
- $this->reverseproxy->subdomain->iterateItems(),
- $this->reverseproxy->reverse->iterateItems(),
- $messages
- );
-
- // Check WebGUI conflicts
+ $this->checkForUniquePortCombos($messages);
$this->checkWebGuiSettings($messages);
-
- // Check for TLS conflicts in Domain
$this->checkDisableTlsConflicts($messages);
-
- // Check DisableSuperuser Port conflicts
$this->checkSuperuserPorts($messages);
-
- // Check Layer4 matchers
$this->checkLayer4Matchers($messages);
return $messages;
diff --git a/www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/Caddy.xml b/www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/Caddy.xml
index c663b3960..8e357ad8e 100644
--- a/www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/Caddy.xml
+++ b/www/caddy/src/opnsense/mvc/app/models/OPNsense/Caddy/Caddy.xml
@@ -1,7 +1,7 @@
//Pischem/caddy
Caddy Reverse Proxy
- 1.3.2
+ 1.3.3
@@ -453,8 +453,35 @@
1
Y
-
+
+ 1
+ 99999
+ Please enter a value between 1 and 99999 or leave empty.
+
+
+ Sequence value has to be unique or empty.
+ UniqueConstraint
+
+
+
+
Y
+ listener_wrappers
+
+ listener_wrappers
+ global
+
+
+
+ Y
+ tcp
+
+ TCP
+ UDP
+
+
+
+
N
Y
Y
@@ -466,7 +493,9 @@
Y
tlssni
+ ANY
DNS
+ HTTP
HTTP (Host Header)
Postgres
Proxy Protocol
@@ -474,11 +503,13 @@
SOCKSv4
SOCKSv5
SSH
- TLS (SNI)
- TLS (inverted SNI)
+ TLS
+ TLS (SNI Client Hello)
+ Wireguard
XMPP
+
Y
,
diff --git a/www/caddy/src/opnsense/mvc/app/views/OPNsense/Caddy/reverse_proxy.volt b/www/caddy/src/opnsense/mvc/app/views/OPNsense/Caddy/reverse_proxy.volt
index ee1b2f5ab..cd7cb0dc3 100644
--- a/www/caddy/src/opnsense/mvc/app/views/OPNsense/Caddy/reverse_proxy.volt
+++ b/www/caddy/src/opnsense/mvc/app/views/OPNsense/Caddy/reverse_proxy.volt
@@ -339,6 +339,14 @@
}
});
+ $("#layer4\\.Matchers").change(function() {
+ if ($(this).val() !== "tlssni" && $(this).val() !== "httphost") {
+ $(".style_matchers").closest('tr').hide();
+ } else {
+ $(".style_matchers").closest('tr').show();
+ }
+ });
+
// Initialize tabs, service control and filter selectpicker
initializeTabs();
updateServiceControlUI('caddy');
@@ -617,8 +625,13 @@
| {{ lang._('ID') }} |
{{ lang._('Enabled') }} |
+ {{ lang._('Sequence') }} |
+ {{ lang._('Routing Type') }} |
+ {{ lang._('Protocol') }} |
+ {{ lang._('Local Port') }} |
{{ lang._('Domain') }} |
- {{ lang._('Matcher') }} |
+ {{ lang._('Matchers') }} |
+ {{ lang._('Invert Matchers') }} |
{{ lang._('Upstream Domain') }} |
{{ lang._('Upstream Port') }} |
{{ lang._('Remote IP') }} |
diff --git a/www/caddy/src/opnsense/service/templates/OPNsense/Caddy/Caddyfile b/www/caddy/src/opnsense/service/templates/OPNsense/Caddy/Caddyfile
index 0fd8fc9fd..62f47f65d 100644
--- a/www/caddy/src/opnsense/service/templates/OPNsense/Caddy/Caddyfile
+++ b/www/caddy/src/opnsense/service/templates/OPNsense/Caddy/Caddyfile
@@ -90,13 +90,27 @@
{% endif %}
{% if generalSettings.EnableLayer4|default("0") == "1" %}
listener_wrappers {
- {# Plug the Layer 4 template in #}
- {% include "OPNsense/Caddy/includeLayer4" %}
-
+ layer4 {
+ import /usr/local/etc/caddy/caddy.d/*.layer4listener
+ {% set context_var = "listener_wrappers" %}
+ {% include "OPNsense/Caddy/includeLayer4" %}
+ {# Empty Route that catches all other traffic #}
+ route
+ }
+ {# Route all other traffic to HTTP App #}
+ tls
}
{% endif %}
}
+ {% if generalSettings.EnableLayer4|default("0") == "1" %}
+ layer4 {
+ import /usr/local/etc/caddy/caddy.d/*.layer4global
+ {% set context_var = "global" %}
+ {% include "OPNsense/Caddy/includeLayer4" %}
+ }
+ {% endif %}
+
{#
# Section: Dynamic DNS Global Configuration
# Purpose: Sets up global configuration for Dynamic DNS. Caddy needs to be compiled with
diff --git a/www/caddy/src/opnsense/service/templates/OPNsense/Caddy/includeLayer4 b/www/caddy/src/opnsense/service/templates/OPNsense/Caddy/includeLayer4
index 7eb367b6b..defaf2528 100644
--- a/www/caddy/src/opnsense/service/templates/OPNsense/Caddy/includeLayer4
+++ b/www/caddy/src/opnsense/service/templates/OPNsense/Caddy/includeLayer4
@@ -1,15 +1,31 @@
{#
-# This file sets up the listener_wrapper for layer 4 routing support.
-# - Section: Servers Global Configuration
-# Also allows for custom configurations with the import statement.
+# This file sets up layer4 routing support.
+# There are two contexts: "listener_wrappers" and "global"
+#
+# "listener_wrappers" multiplexes on OSI Layer 7 on the default HTTP and HTTPS ports and requires a traffic matcher since
+# otherwise the "reverse_proxy" would stop receiving any requests. The "any" Layer 7 matcher is not allowed here.
+# This context allows for matching domains via SNI and route them without terminating TLS.
+#
+# "global" can set up custom ports and also route any OSI Layer 4 TCP/UDP traffic without a matcher.
+# They will be grouped under the same protocol/port combination
+# to allow multiple Layer 7 matchers inside the scope of the same Layer 4 matcher.
+# This context is for advanced usecases where raw TCP/UDP traffic on custom ports should be proxied or load balanced.
#}
-{% set layer4_configs = helpers.toList('Pischem.caddy.reverseproxy.layer4') %}
-{# Nested Macro for proxy definition #}
-{% macro define_proxy(to_domains, to_port, fail_duration, proxy_protocol) %}
+{% set unsorted_layer4_configs = helpers.toList('Pischem.caddy.reverseproxy.layer4') %}
+
+{# Ensure that 'Sequence' is present and converted to an integer in each item #}
+{% for item in unsorted_layer4_configs %}
+ {% set _ = item.update({'Sequence': item.get('Sequence', '0') | int}) %}
+{% endfor %}
+
+{# Sort the configurations based on 'Sequence' #}
+{% set layer4_configs = unsorted_layer4_configs | sort(attribute='Sequence') %}
+
+{% macro define_proxy(layer4, to_domains, to_port, fail_duration, proxy_protocol) %}
proxy {% for domain in to_domains.split(',') %}
- {% set is_ipv6 = (':' in domain) %} {# Check if the domain contains a colon, typical in IPv6 addresses #}
- {{ '[' if is_ipv6 }}{{ domain }}{{ ']' if is_ipv6 }}:{{ to_port }}{% if not loop.last %} {% endif %}
+ {% set is_ipv6 = (':' in domain) %}
+ {{ layer4.Protocol }}/{{ '[' if is_ipv6 }}{{ domain }}{{ ']' if is_ipv6 }}:{{ to_port }}{% if not loop.last %} {% endif %}
{% endfor %} {
{% if fail_duration %}
fail_duration {{ fail_duration }}s
@@ -20,64 +36,73 @@
}
{% endmacro %}
-{# Macro for configuring the proxy with additional remote IP access list #}
-{% macro configure_proxy(to_domains, to_port, remote_ips, fail_duration, proxy_protocol) %}
- {% if remote_ips %}
- {% set ip_list = remote_ips.split(',') %}
- subroute {
- @allowed_ips remote_ip {{ ip_list|join(' ') }}
- route @allowed_ips {
- {# Call Nested Macro #}
- {{ define_proxy(to_domains, to_port, fail_duration, proxy_protocol) }}
+{% macro configure_proxy(layer4, to_domains, to_port, remote_ips, fail_duration, proxy_protocol) %}
+ {% set content %}
+ {% if remote_ips %}
+ {% set ip_list = remote_ips.split(',') %}
+ subroute {
+ @allowed_ips remote_ip {{ ip_list|join(' ') }}
+ route @allowed_ips {
+ {{ define_proxy(layer4, to_domains, to_port, fail_duration, proxy_protocol) }}
+ }
}
- }
+ {% else %}
+ {{ define_proxy(layer4, to_domains, to_port, fail_duration, proxy_protocol) }}
+ {% endif %}
+ {% endset %}
+ {{ content|trim }}
+{% endmacro %}
+
+{% set grouped_configs = {} %}
+{% for layer4 in layer4_configs %}
+ {% if layer4.FromPort and layer4.Protocol and layer4.enabled == "1" %}
+ {% set key = layer4.Protocol ~ '/:' ~ layer4.FromPort %}
+ {% if not key in grouped_configs %}
+ {% set _ = grouped_configs.update({key: []}) %}
+ {% endif %}
+ {% set _ = grouped_configs[key].append(layer4) %}
+ {% endif %}
+{% endfor %}
+
+{% macro handle_special_matchers(layer4) %}
+ {% set invert_prefix = 'not ' if layer4.InvertMatchers == '1' else '' %}
+ {% if layer4.Matchers == 'httphost' %}
+ {{ invert_prefix }}http host {{ layer4.FromDomain.replace(',', ' ') }}
+ {% elif layer4.Matchers == 'tlssni' %}
+ {{ invert_prefix }}tls sni {{ layer4.FromDomain.replace(',', ' ') }}
{% else %}
- {# Call Nested Macro #}
- {{ define_proxy(to_domains, to_port, fail_duration, proxy_protocol) }}
+ {{ invert_prefix }}{{ layer4.Matchers }}
{% endif %}
{% endmacro %}
-{# Set up Layer4 App #}
-layer4 {
- import /usr/local/etc/caddy/caddy.d/*.layer4
- {# 1. loop to handle any traffic matchers that can only be added once since they match all specific protocol traffic. #}
+{% if context_var == 'listener_wrappers' %}
{% for layer4 in layer4_configs %}
- {% if layer4.enabled == "1" and layer4.Matchers not in ['httphost', 'tlssni', 'nottlssni'] %}
- @{{ layer4['@uuid'] }} {{ layer4.Matchers }}
- route @{{ layer4['@uuid'] }} {
- {{ configure_proxy(layer4.ToDomain, layer4.ToPort, layer4.RemoteIp, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
- }
+ {% if layer4.enabled == "1" and layer4.Type == 'listener_wrappers' %}
+ {% if layer4.Matchers != 'any' %}
+ @{{ layer4['@uuid'] }} {{ handle_special_matchers(layer4) }}
+ route @{{ layer4['@uuid'] }} {
+ {{ configure_proxy(layer4, layer4.ToDomain, layer4.ToPort, layer4.RemoteIp, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
+ }
+ {% endif %}
{% endif %}
{% endfor %}
- {# 2. loop to handle http host matchers #}
- {% for layer4 in layer4_configs %}
- {% if layer4.enabled == "1" and layer4.Matchers == 'httphost' %}
- @{{ layer4['@uuid'] }} http host {{ layer4.FromDomain.replace(',', ' ') }}
- route @{{ layer4['@uuid'] }} {
- {{ configure_proxy(layer4.ToDomain, layer4.ToPort, layer4.RemoteIp, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
- }
- {% endif %}
+{% elif context_var == 'global' %}
+ {% for key, layers in grouped_configs.items() %}
+ {{ key }} {
+ {% for layer4 in layers %}
+ {% if layer4.enabled == "1" and layer4.Type == 'global' %}
+ {% if layer4.Matchers != 'any' %}
+ @{{ layer4['@uuid'] }} {{ handle_special_matchers(layer4) }}
+ route @{{ layer4['@uuid'] }} {
+ {{ configure_proxy(layer4, layer4.ToDomain, layer4.ToPort, layer4.RemoteIp, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
+ }
+ {% else %}
+ route {
+ {{ configure_proxy(layer4, layer4.ToDomain, layer4.ToPort, layer4.RemoteIp, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
+ }
+ {% endif %}
+ {% endif %}
+ {% endfor %}
+ }
{% endfor %}
- {# 3. loop to handle tls sni matchers #}
- {% for layer4 in layer4_configs %}
- {% if layer4.enabled == "1" and layer4.Matchers == 'tlssni' %}
- @{{ layer4['@uuid'] }} tls sni {{ layer4.FromDomain.replace(',', ' ') }}
- route @{{ layer4['@uuid'] }} {
- {{ configure_proxy(layer4.ToDomain, layer4.ToPort, layer4.RemoteIp, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
- }
- {% endif %}
- {% endfor %}
- {# 4. loop to handle not tls sni matchers #}
- {% for layer4 in layer4_configs %}
- {% if layer4.enabled == "1" and layer4.Matchers == 'nottlssni' %}
- @{{ layer4['@uuid'] }} not tls sni {{ layer4.FromDomain.replace(',', ' ') }}
- route @{{ layer4['@uuid'] }} {
- {{ configure_proxy(layer4.ToDomain, layer4.ToPort, layer4.RemoteIp, layer4.PassiveHealthFailDuration, layer4.ProxyProtocol) }}
- }
- {% endif %}
- {% endfor %}
- {# Empty Route that catches all other traffic #}
- route
-}
-{# Route all other traffic to HTTP App #}
-tls
+{% endif %}