mirror of
https://github.com/lucaspalomodevelop/core.git
synced 2026-03-15 09:04:39 +00:00
Firewall: Aliases - add BGP ASN type. https://github.com/opnsense/core/issues/5913
This commit is contained in:
parent
b5bda2bda4
commit
b6f95cdea4
@ -36,6 +36,7 @@
|
||||
<geoip>GeoIP</geoip>
|
||||
<networkgroup>Network group</networkgroup>
|
||||
<mac>MAC address</mac>
|
||||
<asn>BGP ASN</asn>
|
||||
<dynipv6host>Dynamic IPv6 Host</dynipv6host>
|
||||
<internal>Internal (automatic)</internal>
|
||||
<external>External (advanced)</external>
|
||||
|
||||
@ -135,6 +135,24 @@ class AliasContentField extends BaseField
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate asn alias options
|
||||
* @param array $data to validate
|
||||
* @return bool|Callback
|
||||
* @throws \OPNsense\Base\ModelException
|
||||
*/
|
||||
private function validateASN($data)
|
||||
{
|
||||
$messages = [];
|
||||
$filter_opts = ["min_range" => 1, "max_range" => 4294967296];
|
||||
foreach ($this->getItems($data) as $asn) {
|
||||
if (filter_var($asn, FILTER_VALIDATE_INT, ["options"=> $filter_opts]) === false) {
|
||||
$messages[] = sprintf(gettext('Entry "%s" is not a valid ASN.'), $asn);
|
||||
}
|
||||
}
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate host options
|
||||
* @param array $data to validate
|
||||
@ -335,6 +353,12 @@ class AliasContentField extends BaseField
|
||||
}
|
||||
]);
|
||||
break;
|
||||
case "asn":
|
||||
$validators[] = new CallbackValidator(["callback" => function ($data) {
|
||||
return $this->validateASN($data);
|
||||
}
|
||||
]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@ -234,6 +234,11 @@
|
||||
$("#alias_type_geoip").show();
|
||||
$("#alias\\.proto").selectpicker('show');
|
||||
break;
|
||||
case 'asn':
|
||||
$("#alias_type_default").show();
|
||||
$("#alias\\.proto").selectpicker('show');
|
||||
$("#copy-paste").show();
|
||||
break;
|
||||
case 'external':
|
||||
break;
|
||||
case 'networkgroup':
|
||||
@ -527,6 +532,7 @@
|
||||
<option value="geoip">{{ lang._('GeoIP') }}</option>
|
||||
<option value="networkgroup">{{ lang._('Network group') }}</option>
|
||||
<option value="mac">{{ lang._('MAC address') }}</option>
|
||||
<option value="asn">{{ lang._('BGP ASN') }}</option>
|
||||
<option value="dynipv6host">{{ lang._('Dynamic IPv6 Host') }}</option>
|
||||
<option value="internal">{{ lang._('Internal (automatic)') }}</option>
|
||||
<option value="external">{{ lang._('External (advanced)') }}</option>
|
||||
|
||||
@ -39,6 +39,7 @@ from dns.exception import DNSException
|
||||
from . import geoip
|
||||
from . import net_wildcard_iterator, AsyncDNSResolver
|
||||
from .arpcache import ArpCache
|
||||
from .bgpasn import BGPASN
|
||||
from .interface import InterfaceParser
|
||||
|
||||
class Alias(object):
|
||||
@ -279,6 +280,8 @@ class Alias(object):
|
||||
return InterfaceParser(self._interface).iter_dynipv6host
|
||||
elif self._type == 'mac':
|
||||
return ArpCache().iter_addresses
|
||||
elif self._type == 'asn':
|
||||
return BGPASN(self._proto).iter_addresses
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
104
src/opnsense/scripts/filter/lib/bgpasn.py
Normal file
104
src/opnsense/scripts/filter/lib/bgpasn.py
Normal file
@ -0,0 +1,104 @@
|
||||
"""
|
||||
Copyright (c) 2022 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 csv
|
||||
import fcntl
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
import syslog
|
||||
import gzip
|
||||
import requests
|
||||
|
||||
class BGPASN:
|
||||
_asn_source = 'https://rulesets.opnsense.org/alias/asn.gz' # source for ASN administration
|
||||
_asn_filename = '/usr/local/share/bgp/asn.csv' # local copy
|
||||
_asn_ttl = (86400 - 90) # validity in seconds of the local copy
|
||||
_asn_fhandle = None # file handle to local copy
|
||||
_asn_db = {} # cache
|
||||
|
||||
@classmethod
|
||||
def _update(cls):
|
||||
do_update = True
|
||||
if os.path.isfile(cls._asn_filename):
|
||||
fstat = os.stat(cls._asn_filename)
|
||||
if (time.time() - fstat.st_mtime) < cls._asn_ttl:
|
||||
do_update = False
|
||||
if do_update:
|
||||
if not os.path.exists(os.path.dirname(cls._asn_filename)):
|
||||
os.makedirs(os.path.dirname(cls._asn_filename))
|
||||
cls._asn_fhandle = open(cls._asn_filename, 'a+')
|
||||
try:
|
||||
fcntl.flock(cls._asn_fhandle, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except IOError:
|
||||
# other process is already creating the cache, wait, let the other process do it's work and return.
|
||||
fcntl.flock(cls._asn_fhandle, fcntl.LOCK_EX)
|
||||
fcntl.flock(cls._asn_fhandle, fcntl.LOCK_UN)
|
||||
return
|
||||
|
||||
req = requests.get(url=cls._asn_source, stream=True, timeout=20)
|
||||
if req.status_code == 200:
|
||||
gf = gzip.GzipFile(mode='r', fileobj=req.raw)
|
||||
cls._asn_fhandle.seek(0)
|
||||
cls._asn_fhandle.truncate()
|
||||
count = 0
|
||||
for line in gf:
|
||||
parts = line.decode().strip().split()
|
||||
if len(parts) == 2:
|
||||
cls._asn_fhandle.write("%s,%s\n" % tuple(parts))
|
||||
count += 1
|
||||
fcntl.flock(cls._asn_fhandle, fcntl.LOCK_UN)
|
||||
syslog.syslog(syslog.LOG_NOTICE, 'dowloaded ASN list (%d entries)' % count)
|
||||
else:
|
||||
syslog.syslog(
|
||||
syslog.LOG_ERR,
|
||||
'error fetching BGP ASN url %s [http_code:%s]' % (cls._asn_source, req.status_code)
|
||||
)
|
||||
raise IOError('error fetching BGP ASN url %s' % cls._asn_source)
|
||||
else:
|
||||
cls._asn_fhandle = open(cls._asn_filename, 'rt')
|
||||
|
||||
def __init__(self, proto='IPv4'):
|
||||
self.proto = proto.split(',')
|
||||
if self._asn_fhandle is None:
|
||||
# update local asn list if needed, return a file pointer to a local csv file for reading
|
||||
self._update()
|
||||
|
||||
def iter_addresses(self, asn):
|
||||
if len(self._asn_db) == 0:
|
||||
self._asn_fhandle.seek(0)
|
||||
for row in csv.reader(self._asn_fhandle, delimiter=',', quotechar='"'):
|
||||
if len(row) == 2:
|
||||
if row[1] not in self._asn_db:
|
||||
self._asn_db[row[1]] = []
|
||||
self._asn_db[row[1]].append(row[0])
|
||||
|
||||
if asn in self._asn_db:
|
||||
for address in self._asn_db[asn]:
|
||||
if 'IPv4' in self.proto and address.find(':') == -1:
|
||||
yield address
|
||||
elif 'IPv6' in self.proto and address.find(':') > -1:
|
||||
yield address
|
||||
@ -28,7 +28,7 @@
|
||||
<proto>{{ alias.proto|e }}</proto>
|
||||
{% endif %}{% if alias.updatefreq %}
|
||||
<ttl>{{ alias.updatefreq|float * 86400 }}</ttl>
|
||||
{% elif alias.type == 'geoip' %}
|
||||
{% elif alias.type in ['geoip', 'asn'] %}
|
||||
<ttl>86400</ttl>
|
||||
{% elif alias.type == 'host' %}
|
||||
<ttl>{{ system.aliasesresolveinterval|default('300') }}</ttl>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user