+ {{lang._('Select the interface for the V6 dynamic IP')}}
+
+
+
+
+
+
diff --git a/src/opnsense/scripts/filter/lib/alias.py b/src/opnsense/scripts/filter/lib/alias.py
index 70ffa6a56..24c80def0 100755
--- a/src/opnsense/scripts/filter/lib/alias.py
+++ b/src/opnsense/scripts/filter/lib/alias.py
@@ -37,6 +37,7 @@ from hashlib import md5
from . import geoip
from . import net_wildcard_iterator, AsyncDNSResolver
from .arpcache import ArpCache
+from .interface import InterfaceParser
class Alias(object):
def __init__(self, elem, known_aliases=[], ttl=-1, ssl_no_verify=False, timeout=120):
@@ -56,6 +57,7 @@ class Alias(object):
self._timeout = timeout
self._name = None
self._type = None
+ self._interface = None
self._proto = 'IPv4,IPv6'
self._items = list()
self._resolve_content = set()
@@ -66,6 +68,8 @@ class Alias(object):
self._proto = subelem.text
elif subelem.tag == 'name':
self._name = subelem.text
+ elif subelem.tag == 'interface':
+ self._interface = subelem.text
elif subelem.tag == 'ttl':
tmp = subelem.text.strip()
if len(tmp.split('.')) <= 2 and tmp.replace('.', '').isdigit():
@@ -263,6 +267,8 @@ class Alias(object):
return self._fetch_url
elif self._type == 'geoip':
return self._fetch_geo
+ elif self._type == 'dynipv6host':
+ return InterfaceParser(self._interface).iter_dynipv6host
elif self._type == 'mac':
return ArpCache().iter_addresses
else:
diff --git a/src/opnsense/scripts/filter/lib/interface.py b/src/opnsense/scripts/filter/lib/interface.py
new file mode 100755
index 000000000..88ce8c9ba
--- /dev/null
+++ b/src/opnsense/scripts/filter/lib/interface.py
@@ -0,0 +1,75 @@
+"""
+ Copyright (c) 2021 Ad Schellevis
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+"""
+import ipaddress
+import subprocess
+
+
+class InterfaceParser:
+ """ Interface address parser
+ """
+ _ipv6_networks = dict()
+
+ @classmethod
+ def _update(cls):
+ this_interface = None
+ for line in subprocess.run(['/sbin/ifconfig'], capture_output=True, text=True).stdout.split('\n'):
+ if not line.startswith("\t") and line.find(':') > -1:
+ this_interface = line.strip().split(':')[0]
+ elif this_interface is not None and line.startswith("\tinet6"):
+ parts = line.strip().split()
+ addr = None
+ mask = None
+ for i in range(len(parts)):
+ if parts[i] == 'inet6':
+ addr = parts[i+1].split("%")[0]
+ elif parts[i] == 'prefixlen':
+ mask = parts[i+1]
+ if this_interface not in cls._ipv6_networks:
+ cls._ipv6_networks[this_interface] = []
+ if mask and addr:
+ cls._ipv6_networks[this_interface].append({"addr": ipaddress.IPv6Address(addr), "mask": mask})
+
+ def __init__(self, interface):
+ self._interface = interface
+ # collect addresses on class init (singleton)
+ if len(self._ipv6_networks) == 0:
+ self._update()
+
+ def iter_dynipv6host(self, pattern):
+ if self._interface in self._ipv6_networks:
+ for network in self._ipv6_networks[self._interface]:
+ # only global addresses apply
+ if network["addr"].is_global:
+ base_mask = int(network["mask"])
+ base_size=int((128-base_mask)/16)
+ offset_address = ipaddress.IPv6Address('0' + pattern.split("/")[0])
+ calculated_address = ':'.join(
+ network["addr"].exploded.split(':')[:8-base_size] +
+ offset_address.exploded.split(':')[8-base_size:]
+ )
+ calculated_mask = pattern.split("/")[1] if pattern.find("/") > -1 else "128"
+ yield "%s/%s" % (calculated_address, calculated_mask)
diff --git a/src/opnsense/service/templates/OPNsense/Filter/filter_tables.conf b/src/opnsense/service/templates/OPNsense/Filter/filter_tables.conf
index 4deb0b013..a28123ac9 100644
--- a/src/opnsense/service/templates/OPNsense/Filter/filter_tables.conf
+++ b/src/opnsense/service/templates/OPNsense/Filter/filter_tables.conf
@@ -5,6 +5,7 @@
{% endif %}
{% set new_style_aliases = 0 %}
+{% from 'OPNsense/Macros/interface.macro' import physical_interface %}
{% if helpers.exists('OPNsense.Firewall.Alias.aliases.alias') %}
{% set new_style_aliases = OPNsense.Firewall.Alias.aliases.alias|length %}
{% for alias in helpers.toList('OPNsense.Firewall.Alias.aliases.alias') %}
@@ -21,6 +22,8 @@
{{ alias.content|e|encode_idna }}
{% elif alias.content %}
{{ alias.content|e|encode_idna }}
+{% endif %}{% if alias.interface and alias.type == 'dynipv6host' %}
+ {{ physical_interface(alias.interface)|default('LAN')}}
{% endif %}{% if alias.proto %}
{{ alias.proto|e }}
{% endif %}{% if alias.updatefreq %}
@@ -31,6 +34,8 @@
{{ system.aliasesresolveinterval|default('300') }}
{% elif alias.type == 'mac' %}
30
+{% elif alias.type == 'dynipv6host' %}
+ 1
{% endif %}
{% endif %}