mirror of
https://github.com/lucaspalomodevelop/core.git
synced 2026-03-18 10:35:27 +00:00
Firewall / Aliases - Dynamic Ipv6 fw rules handling
for https://github.com/opnsense/core/issues/4923 , https://github.com/opnsense/core/pull/4941 o add validations for new type o rename dyninterface to interface to make the attribute more generic (in case of future use) o move address logic to interface class
This commit is contained in:
parent
7311b413f6
commit
89a2a8d51b
@ -37,8 +37,13 @@
|
||||
<networkgroup>Network group</networkgroup>
|
||||
<mac>MAC address</mac>
|
||||
<external>External (advanced)</external>
|
||||
<dynipv6host>IPv6 Dynamic Host</dynipv6host>
|
||||
<dynipv6host>IPv6 Dynamic Host</dynipv6host>
|
||||
</OptionValues>
|
||||
<Constraints>
|
||||
<check001>
|
||||
<reference>interface.check001</reference>
|
||||
</check001>
|
||||
</Constraints>
|
||||
</type>
|
||||
<proto type="OptionField">
|
||||
<Multiple>Y</Multiple>
|
||||
@ -47,8 +52,16 @@
|
||||
<IPv6>IPv6</IPv6>
|
||||
</OptionValues>
|
||||
</proto>
|
||||
<dyninterface type="InterfaceField">
|
||||
</dyninterface>
|
||||
<interface type="InterfaceField">
|
||||
<Constraints>
|
||||
<check001>
|
||||
<ValidationMessage>IPv6 Dynamic Host require an interface to track.</ValidationMessage>
|
||||
<type>SetIfConstraint</type>
|
||||
<field>type</field>
|
||||
<check>dynipv6host</check>
|
||||
</check001>
|
||||
</Constraints>
|
||||
</interface>
|
||||
<counters type="BooleanField">
|
||||
<default>0</default>
|
||||
</counters>
|
||||
|
||||
@ -218,6 +218,26 @@ class AliasContentField extends BaseField
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate partial ipv6 network definition
|
||||
* @param array $data to validate
|
||||
* @return bool|Callback
|
||||
* @throws \OPNsense\Base\ModelException
|
||||
*/
|
||||
private function validatePartialIPv6Network($data)
|
||||
{
|
||||
$messages = array();
|
||||
foreach ($this->getItems($data) as $pnetwork) {
|
||||
if (!Util::isSubnet("0000".$pnetwork)) {
|
||||
$messages[] = sprintf(
|
||||
gettext('Entry "%s" is not a valid partial ipv6 net definition (e.g. ::1000/64).'),
|
||||
$pnetwork
|
||||
);
|
||||
}
|
||||
}
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate host options
|
||||
* @param array $data to validate
|
||||
@ -300,6 +320,12 @@ class AliasContentField extends BaseField
|
||||
}
|
||||
]);
|
||||
break;
|
||||
case "dynipv6host":
|
||||
$validators[] = new CallbackValidator(["callback" => function ($data) {
|
||||
return $this->validatePartialIPv6Network($data);
|
||||
}
|
||||
]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@ -195,6 +195,7 @@
|
||||
$("#alias\\.type").change(function(){
|
||||
$(".alias_type").hide();
|
||||
$("#row_alias\\.updatefreq").hide();
|
||||
$("#row_alias\\.interface").hide();
|
||||
$("#copy-paste").hide();
|
||||
switch ($(this).val()) {
|
||||
case 'geoip':
|
||||
@ -207,19 +208,19 @@
|
||||
$("#alias_type_networkgroup").show();
|
||||
$("#alias\\.proto").selectpicker('hide');
|
||||
break;
|
||||
case 'dynipv6host':
|
||||
$("#row_alias\\.interface").show();
|
||||
$("#alias_type_default").show();
|
||||
break;
|
||||
case 'urltable':
|
||||
$("#row_alias\\.updatefreq").show();
|
||||
/* FALLTROUGH */
|
||||
default:
|
||||
$("#alias_type_default").show();
|
||||
$("#alias\\.proto").selectpicker('hide');
|
||||
$("#copy-paste").show();
|
||||
break;
|
||||
}
|
||||
if ($(this).val() === 'dynipv6host') {
|
||||
$("#row_alias\\.dyninterface").show();
|
||||
} else {
|
||||
$("#row_alias\\.dyninterface").hide();
|
||||
}
|
||||
if ($(this).val() === 'port') {
|
||||
$("#row_alias\\.counters").hide();
|
||||
} else {
|
||||
@ -477,7 +478,7 @@
|
||||
<option value="urltable">{{ lang._('URL Table (IPs)') }}</option>
|
||||
<option value="geoip">{{ lang._('GeoIP') }}</option>
|
||||
<option value="networkgroup">{{ lang._('Network group') }}</option>
|
||||
<option value="dynipv6host">{{ lang._('Dynamic IPv6 Host') }}</option>
|
||||
<option value="dynipv6host">{{ lang._('Dynamic IPv6 Host') }}</option>
|
||||
<option value="external">{{ lang._('External (advanced)') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
@ -697,21 +698,21 @@
|
||||
<span class="help-block" id="help_block_alias.content"></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="row_alias.dyninterface">
|
||||
<tr id="row_alias.interface">
|
||||
<td>
|
||||
<div class="alias dyninterface" id="alias_dyninterface">
|
||||
<a id="help_for_alias.dyninterface" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a>
|
||||
<b>{{lang._('dyninterface')}}</b>
|
||||
<div class="alias interface" id="alias_interface">
|
||||
<a id="help_for_alias.interface" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a>
|
||||
<b>{{lang._('Interface')}}</b>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<select class="selectpicker" id="alias.dyninterface" data-width="200px"></select>
|
||||
<div class="hidden" data-for="help_for_alias.dyninterface">
|
||||
<small>{{lang._('Select the dyninterface for the V6 dynamic IP')}}</small>
|
||||
<select class="selectpicker" id="alias.interface" data-width="200px"></select>
|
||||
<div class="hidden" data-for="help_for_alias.interface">
|
||||
<small>{{lang._('Select the interface for the V6 dynamic IP')}}</small>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="help-block" id="help_block_alias.enabled"></span>
|
||||
<span class="help-block" id="help_block_alias.interface"></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="row_alias.counters">
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
"""
|
||||
import socket
|
||||
import fcntl
|
||||
import struct
|
||||
import struct
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
@ -36,10 +36,12 @@ import requests
|
||||
import ipaddress
|
||||
import dns.resolver
|
||||
import syslog
|
||||
import subprocess
|
||||
import subprocess
|
||||
from hashlib import md5
|
||||
from . import geoip
|
||||
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):
|
||||
@ -61,7 +63,7 @@ class Alias(object):
|
||||
self._timeout = timeout
|
||||
self._name = None
|
||||
self._type = None
|
||||
self._dyninterface = None
|
||||
self._interface = None
|
||||
self._proto = 'IPv4,IPv6'
|
||||
self._items = list()
|
||||
self._resolve_content = set()
|
||||
@ -72,8 +74,8 @@ class Alias(object):
|
||||
self._proto = subelem.text
|
||||
elif subelem.tag == 'name':
|
||||
self._name = subelem.text
|
||||
elif subelem.tag == 'dyninterface':
|
||||
self._dyninterface = 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():
|
||||
@ -266,31 +268,6 @@ class Alias(object):
|
||||
# return the addresses and networks of this alias
|
||||
return list(self._resolve_content)
|
||||
|
||||
def _fetch_dynipv6(self, address):
|
||||
|
||||
#get the interface /64 address
|
||||
address = address.strip()
|
||||
sp = subprocess.run(['/sbin/ifconfig', self._dyninterface,'inet6'], capture_output=True, text=True)
|
||||
for line in sp.stdout.split('\n'):
|
||||
if line.find('prefixlen 64') > -1:
|
||||
i_address = line.split(' ')[1]
|
||||
if i_address[0] == '1' or i_address[0] == '2' or i_address[0] == 3:
|
||||
break
|
||||
# split the mask - if no mask it's a /64
|
||||
if address.find('/') > -1:
|
||||
l_address,l_mask = address.split('/')
|
||||
else:
|
||||
l_mask = '64'
|
||||
l_address = address
|
||||
|
||||
base_mask = int(l_mask)
|
||||
base_address = ipaddress.ip_address(i_address)
|
||||
base_size=int((128-base_mask)/16)
|
||||
combine_address='0'+l_address
|
||||
ipv6_addr = ipaddress.ip_address(combine_address)
|
||||
calculated_address = ':'.join(base_address.exploded.split(':')[:8-base_size] + ipv6_addr.exploded.split(':')[8-base_size:])
|
||||
for address in self._parse_address(calculated_address):
|
||||
yield address
|
||||
def get_parser(self):
|
||||
""" fetch address parser to use, None if alias type is not handled here
|
||||
:return: function or None
|
||||
@ -302,7 +279,7 @@ class Alias(object):
|
||||
elif self._type == 'geoip':
|
||||
return self._fetch_geo
|
||||
elif self._type == 'dynipv6host':
|
||||
return self._fetch_dynipv6
|
||||
return InterfaceParser(self._interface).iter_dynipv6host
|
||||
elif self._type == 'mac':
|
||||
return ArpCache().iter_addresses
|
||||
else:
|
||||
|
||||
74
src/opnsense/scripts/filter/lib/interface.py
Normal file
74
src/opnsense/scripts/filter/lib/interface.py
Normal file
@ -0,0 +1,74 @@
|
||||
"""
|
||||
Copyright (c) 2021 Ad Schellevis <ad@opnsense.org>
|
||||
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 and pattern.find("/") > -1:
|
||||
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:]
|
||||
)
|
||||
yield "%s/%s" % (calculated_address, pattern.split("/")[1])
|
||||
@ -6,7 +6,7 @@
|
||||
</general>
|
||||
{% set new_style_aliases = 0 %}
|
||||
{# Macro import #}
|
||||
{% from 'OPNsense/Macros/interface.macro' import physical_interface %}
|
||||
{% 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') %}
|
||||
@ -23,8 +23,8 @@
|
||||
<aliasurl>{{ alias.content|e|encode_idna }}</aliasurl>
|
||||
{% elif alias.content %}
|
||||
<address>{{ alias.content|e|encode_idna }}</address>
|
||||
{% endif %}{% if alias.dyninterface %}
|
||||
<dyninterface>{{ physical_interface(alias.dyninterface)|default('LAN')
|
||||
{% endif %}{% if alias.interface and alias.type == 'dynipv6host' %}
|
||||
<interface>{{ physical_interface(alias.interface)|default('LAN')}}</interface>
|
||||
{% endif %}{% if alias.proto %}
|
||||
<proto>{{ alias.proto|e }}</proto>
|
||||
{% endif %}{% if alias.updatefreq %}
|
||||
@ -35,6 +35,8 @@
|
||||
<ttl>{{ system.aliasesresolveinterval|default('300') }}</ttl>
|
||||
{% elif alias.type == 'mac' %}
|
||||
<ttl>30</ttl>
|
||||
{% elif alias.type == 'dynipv6host' %}
|
||||
<ttl>1</ttl>
|
||||
{% endif %}
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user