Skip to content

Commit

Permalink
Merge branch 'fail2ban:master' into crowdsec
Browse files Browse the repository at this point in the history
  • Loading branch information
ne20002 committed Jan 31, 2023
2 parents 94b7e3f + f93a538 commit 43aced4
Show file tree
Hide file tree
Showing 4 changed files with 280 additions and 28 deletions.
5 changes: 5 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ ver. 1.0.3-dev-1 (20??/??/??) - development nightly edition
-----------

### Fixes
* circumvent SEGFAULT in a python's socket module by getaddrinfo with disabled IPv6 (gh-3438)

### New Features and Enhancements
* better auto-detection for IPv6 support (`allowipv6 = auto` by default), trying to check sysctl net.ipv6.conf.all.disable_ipv6
(value read from `/proc/sys/net/ipv6/conf/all/disable_ipv6`) if available, otherwise seeks over local IPv6 from network interfaces
if available for platform and uses DNS to find local IPv6 as a fallback only
* improve `ignoreself` by considering all local addresses from network interfaces additionally to IPs from hostnames (gh-3132)


ver. 1.0.2 (2022/11/09) - finally-war-game-test-tape-not-a-nuclear-alarm
Expand Down
253 changes: 228 additions & 25 deletions fail2ban/server/ipdns.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@ def dnsToIp(dns):
# retrieve ips
ips = set()
saveerr = None
for fam, ipfam in ((socket.AF_INET, IPAddr.FAM_IPv4), (socket.AF_INET6, IPAddr.FAM_IPv6)):
for fam in ((socket.AF_INET,socket.AF_INET6) if DNSUtils.IPv6IsAllowed() else (socket.AF_INET,)):
try:
for result in socket.getaddrinfo(dns, None, fam, 0, socket.IPPROTO_TCP):
# if getaddrinfo returns something unexpected:
if len(result) < 4 or not len(result[4]): continue
# get ip from `(2, 1, 6, '', ('127.0.0.1', 0))`,be sure we've an ip-string
# (some python-versions resp. host configurations causes returning of integer there):
ip = IPAddr(str(result[4][0]), ipfam)
ip = IPAddr(str(result[4][0]), IPAddr._AF2FAM(fam))
if ip.isValid:
ips.add(ip)
except Exception as e:
Expand Down Expand Up @@ -154,17 +154,18 @@ def getHostname(fqdn=True):
# try find cached own hostnames (this tuple-key cannot be used elsewhere):
key = ('self','hostname', fqdn)
name = DNSUtils.CACHE_ipToName.get(key)
if name is not None:
return name
# get it using different ways (hostname, fully-qualified or vice versa):
if name is None:
name = ''
for hostname in (
(getfqdn, socket.gethostname) if fqdn else (socket.gethostname, getfqdn)
):
try:
name = hostname()
break
except Exception as e: # pragma: no cover
logSys.warning("Retrieving own hostnames failed: %s", e)
name = ''
for hostname in (
(getfqdn, socket.gethostname) if fqdn else (socket.gethostname, getfqdn)
):
try:
name = hostname()
break
except Exception as e: # pragma: no cover
logSys.warning("Retrieving own hostnames failed: %s", e)
# cache and return :
DNSUtils.CACHE_ipToName.set(key, name)
return name
Expand All @@ -177,15 +178,35 @@ def getSelfNames():
"""Get own host names of self"""
# try find cached own hostnames:
names = DNSUtils.CACHE_ipToName.get(DNSUtils._getSelfNames_key)
if names is not None:
return names
# get it using different ways (a set with names of localhost, hostname, fully qualified):
if names is None:
names = set([
'localhost', DNSUtils.getHostname(False), DNSUtils.getHostname(True)
]) - set(['']) # getHostname can return ''
names = set([
'localhost', DNSUtils.getHostname(False), DNSUtils.getHostname(True)
]) - set(['']) # getHostname can return ''
# cache and return :
DNSUtils.CACHE_ipToName.set(DNSUtils._getSelfNames_key, names)
return names

# key to find cached network interfaces IPs (this tuple-key cannot be used elsewhere):
_getNetIntrfIPs_key = ('netintrf','ips')

@staticmethod
def getNetIntrfIPs():
"""Get own IP addresses of self"""
# to find cached own IPs:
ips = DNSUtils.CACHE_nameToIp.get(DNSUtils._getNetIntrfIPs_key)
if ips is not None:
return ips
# try to obtain from network interfaces if possible (implemented for this platform):
try:
ips = IPAddrSet([a for ni, a in DNSUtils._NetworkInterfacesAddrs()])
except:
ips = IPAddrSet()
# cache and return :
DNSUtils.CACHE_nameToIp.set(DNSUtils._getNetIntrfIPs_key, ips)
return ips

# key to find cached own IPs (this tuple-key cannot be used elsewhere):
_getSelfIPs_key = ('self','ips')

Expand All @@ -194,20 +215,54 @@ def getSelfIPs():
"""Get own IP addresses of self"""
# to find cached own IPs:
ips = DNSUtils.CACHE_nameToIp.get(DNSUtils._getSelfIPs_key)
# get it using different ways (a set with IPs of localhost, hostname, fully qualified):
if ips is None:
ips = set()
for hostname in DNSUtils.getSelfNames():
try:
ips |= set(DNSUtils.textToIp(hostname, 'yes'))
except Exception as e: # pragma: no cover
logSys.warning("Retrieving own IPs of %s failed: %s", hostname, e)
if ips is not None:
return ips
# firstly try to obtain from network interfaces if possible (implemented for this platform):
ips = IPAddrSet(DNSUtils.getNetIntrfIPs())
# extend it using different ways (a set with IPs of localhost, hostname, fully qualified):
for hostname in DNSUtils.getSelfNames():
try:
ips |= IPAddrSet(DNSUtils.dnsToIp(hostname))
except Exception as e: # pragma: no cover
logSys.warning("Retrieving own IPs of %s failed: %s", hostname, e)
# cache and return :
DNSUtils.CACHE_nameToIp.set(DNSUtils._getSelfIPs_key, ips)
return ips

_IPv6IsAllowed = None

@staticmethod
def _IPv6IsSupportedBySystem():
if not socket.has_ipv6:
return False
# try to check sysctl net.ipv6.conf.all.disable_ipv6:
try:
with open('/proc/sys/net/ipv6/conf/all/disable_ipv6', 'rb') as f:
# if 1 - disabled, 0 - enabled
return not int(f.read())
except:
pass
s = None
try:
# try to create INET6 socket:
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
# bind it to free port for any interface supporting IPv6:
s.bind(("", 0));
return True
except Exception as e: # pragma: no cover
if hasattr(e, 'errno'):
import errno
# negative (-9 'Address family not supported', etc) or not available/supported:
if e.errno < 0 or e.errno in (errno.EADDRNOTAVAIL, errno.EAFNOSUPPORT):
return False
# in use:
if e.errno in (errno.EADDRINUSE, errno.EACCES): # normally unreachable (free port and root)
return True
finally:
if s: s.close()
# unable to detect:
return None

@staticmethod
def setIPv6IsAllowed(value):
DNSUtils._IPv6IsAllowed = value
Expand All @@ -224,7 +279,17 @@ def IPv6IsAllowed():
v = DNSUtils.CACHE_nameToIp.get(DNSUtils._IPv6IsAllowed_key)
if v is not None:
return v
v = any((':' in ip.ntoa) for ip in DNSUtils.getSelfIPs())
v = DNSUtils._IPv6IsSupportedBySystem()
if v is None:
# detect by IPs of host:
ips = DNSUtils.getNetIntrfIPs()
if not ips:
DNSUtils._IPv6IsAllowed = True; # avoid self recursion from getSelfIPs -> dnsToIp -> IPv6IsAllowed
try:
ips = DNSUtils.getSelfIPs()
finally:
DNSUtils._IPv6IsAllowed = None
v = any((':' in ip.ntoa) for ip in ips)
DNSUtils.CACHE_nameToIp.set(DNSUtils._IPv6IsAllowed_key, v)
return v

Expand Down Expand Up @@ -255,6 +320,9 @@ class IPAddr(object):
CIDR_UNSPEC = -1
FAM_IPv4 = CIDR_RAW - socket.AF_INET
FAM_IPv6 = CIDR_RAW - socket.AF_INET6
@staticmethod
def _AF2FAM(v):
return IPAddr.CIDR_RAW - v

def __new__(cls, ipstr, cidr=CIDR_UNSPEC):
if cidr == IPAddr.CIDR_UNSPEC and isinstance(ipstr, (tuple, list)):
Expand Down Expand Up @@ -545,6 +613,9 @@ def contains(self, ip):
"""
return isinstance(ip, IPAddr) and (ip == self or ip.isInNet(self))

def __contains__(self, ip):
return self.contains(ip)

# Pre-calculated map: addr to maskplen
def __getMaskMap():
m6 = (1 << 128)-1
Expand Down Expand Up @@ -594,3 +665,135 @@ def searchIP(text):

# An IPv4 compatible IPv6 to be reused
IPAddr.IP6_4COMPAT = IPAddr("::ffff:0:0", 96)


class IPAddrSet(set):

hasSubNet = False

def __init__(self, ips=[]):
ips2 = set()
for ip in ips:
if not isinstance(ip, IPAddr): ip = IPAddr(ip)
ips2.add(ip)
self.hasSubNet |= not ip.isSingle
set.__init__(self, ips2)

def add(self, ip):
if not isinstance(ip, IPAddr): ip = IPAddr(ip)
self.hasSubNet |= not ip.isSingle
set.add(self, ip)

def __contains__(self, ip):
if not isinstance(ip, IPAddr): ip = IPAddr(ip)
# IP can be found directly or IP is in each subnet:
return set.__contains__(self, ip) or (self.hasSubNet and any(n.contains(ip) for n in self))


def _NetworkInterfacesAddrs(withMask=False):

# Closure implementing lazy load modules and libc and define _NetworkInterfacesAddrs on demand:
# Currently tested on Linux only (TODO: implement for MacOS, Solaris, etc)
try:
from ctypes import (
Structure, Union, POINTER,
pointer, get_errno, cast,
c_ushort, c_byte, c_void_p, c_char_p, c_uint, c_int, c_uint16, c_uint32
)
import ctypes.util
import ctypes

class struct_sockaddr(Structure):
_fields_ = [
('sa_family', c_ushort),
('sa_data', c_byte * 14),]

class struct_sockaddr_in(Structure):
_fields_ = [
('sin_family', c_ushort),
('sin_port', c_uint16),
('sin_addr', c_byte * 4)]

class struct_sockaddr_in6(Structure):
_fields_ = [
('sin6_family', c_ushort),
('sin6_port', c_uint16),
('sin6_flowinfo', c_uint32),
('sin6_addr', c_byte * 16),
('sin6_scope_id', c_uint32)]

class union_ifa_ifu(Union):
_fields_ = [
('ifu_broadaddr', POINTER(struct_sockaddr)),
('ifu_dstaddr', POINTER(struct_sockaddr)),]

class struct_ifaddrs(Structure):
pass
struct_ifaddrs._fields_ = [
('ifa_next', POINTER(struct_ifaddrs)),
('ifa_name', c_char_p),
('ifa_flags', c_uint),
('ifa_addr', POINTER(struct_sockaddr)),
('ifa_netmask', POINTER(struct_sockaddr)),
('ifa_ifu', union_ifa_ifu),
('ifa_data', c_void_p),]

libc = ctypes.CDLL(ctypes.util.find_library('c') or "")
if not libc.getifaddrs: # pragma: no cover
raise NotImplementedError('libc.getifaddrs is not available')

def ifap_iter(ifap):
ifa = ifap.contents
while True:
yield ifa
if not ifa.ifa_next:
break
ifa = ifa.ifa_next.contents

def getfamaddr(ifa, withMask=False):
sa = ifa.ifa_addr.contents
fam = sa.sa_family
if fam == socket.AF_INET:
sa = cast(pointer(sa), POINTER(struct_sockaddr_in)).contents
addr = socket.inet_ntop(fam, sa.sin_addr)
if withMask:
nm = ifa.ifa_netmask.contents
if nm is not None and nm.sa_family == socket.AF_INET:
nm = cast(pointer(nm), POINTER(struct_sockaddr_in)).contents
addr += '/'+socket.inet_ntop(fam, nm.sin_addr)
return IPAddr(addr)
elif fam == socket.AF_INET6:
sa = cast(pointer(sa), POINTER(struct_sockaddr_in6)).contents
addr = socket.inet_ntop(fam, sa.sin6_addr)
if withMask:
nm = ifa.ifa_netmask.contents
if nm is not None and nm.sa_family == socket.AF_INET6:
nm = cast(pointer(nm), POINTER(struct_sockaddr_in6)).contents
addr += '/'+socket.inet_ntop(fam, nm.sin6_addr)
return IPAddr(addr)
return None

def _NetworkInterfacesAddrs(withMask=False):
ifap = POINTER(struct_ifaddrs)()
result = libc.getifaddrs(pointer(ifap))
if result != 0:
raise OSError(get_errno())
del result
try:
for ifa in ifap_iter(ifap):
name = ifa.ifa_name.decode("UTF-8")
addr = getfamaddr(ifa, withMask)
if addr:
yield name, addr
finally:
libc.freeifaddrs(ifap)

except Exception as e: # pragma: no cover
_init_error = NotImplementedError(e)
def _NetworkInterfacesAddrs():
raise _init_error

DNSUtils._NetworkInterfacesAddrs = staticmethod(_NetworkInterfacesAddrs);
return _NetworkInterfacesAddrs(withMask)

DNSUtils._NetworkInterfacesAddrs = staticmethod(_NetworkInterfacesAddrs);
3 changes: 1 addition & 2 deletions fail2ban/server/jails.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ def add(self, name, backend, db=None):
"""
with self.__lock:
if name in self._jails:
if noduplicates:
raise DuplicateJailException(name)
raise DuplicateJailException(name)
else:
self._jails[name] = Jail(name, backend, db)

Expand Down

0 comments on commit 43aced4

Please sign in to comment.