actions support IPv6 now:

- introduced "conditional" sections, see for example `[Init?family=inet6]`;
  - iptables-common and other iptables config(s) made IPv6 capable;
  - several small code optimizations;
* all test cases passed (py3.x compatible);
pull/1414/head
sebres 2016-05-10 18:40:07 +02:00
parent 75028585c0
commit 504e5ba6f2
8 changed files with 176 additions and 72 deletions

View File

@ -6,6 +6,9 @@
# used in all iptables based actions by default. # used in all iptables based actions by default.
# #
# The user can override the defaults in iptables-common.local # The user can override the defaults in iptables-common.local
#
# Modified: Alexander Koeppe <format_c@online.de>, Serg G. Brester <serg.brester@sebres.de>
# made config file IPv6 capable (see new section Init?family=inet6)
[INCLUDES] [INCLUDES]
@ -13,6 +16,7 @@ after = iptables-blocktype.local
iptables-common.local iptables-common.local
# iptables-blocktype.local is obsolete # iptables-blocktype.local is obsolete
[Init] [Init]
# Option: chain # Option: chain
@ -62,3 +66,19 @@ lockingopt = -w
# Notes.: Actual command to be executed, including common to all calls options # Notes.: Actual command to be executed, including common to all calls options
# Values: STRING # Values: STRING
iptables = iptables <lockingopt> iptables = iptables <lockingopt>
[Init?family=inet6]
# Option: blocktype (ipv6)
# Note: This is what the action does with rules. This can be any jump target
# as per the iptables man page (section 8). Common values are DROP
# REJECT, REJECT --reject-with icmp6-port-unreachable
# Values: STRING
blocktype = REJECT --reject-with icmp6-port-unreachable
# Option: iptables (ipv6)
# Notes.: Actual command to be executed, including common to all calls options
# Values: STRING
iptables = ip6tables <lockingopt>

View File

@ -12,6 +12,9 @@
# #
# If you are running on an older kernel you make need to patch in external # If you are running on an older kernel you make need to patch in external
# modules which probably won't be protocol version 6. # modules which probably won't be protocol version 6.
#
# Modified: Alexander Koeppe <format_c@online.de>, Serg G. Brester <serg.brester@sebres.de>
# made config file IPv6 capable (see new section Init?family=inet6)
[INCLUDES] [INCLUDES]
@ -23,16 +26,16 @@ before = iptables-common.conf
# Notes.: command executed once at the start of Fail2Ban. # Notes.: command executed once at the start of Fail2Ban.
# Values: CMD # Values: CMD
# #
actionstart = ipset create f2b-<name> hash:ip timeout <bantime> actionstart = ipset create <ipmset> hash:ip timeout <bantime><familyopt>
<iptables> -I <chain> -m set --match-set f2b-<name> src -j <blocktype> <iptables> -I <chain> -m set --match-set <ipmset> src -j <blocktype>
# Option: actionstop # Option: actionstop
# Notes.: command executed once at the end of Fail2Ban # Notes.: command executed once at the end of Fail2Ban
# Values: CMD # Values: CMD
# #
actionstop = <iptables> -D <chain> -m set --match-set f2b-<name> src -j <blocktype> actionstop = <iptables> -D <chain> -m set --match-set <ipmset> src -j <blocktype>
ipset flush f2b-<name> ipset flush <ipmset>
ipset destroy f2b-<name> ipset destroy <ipmset>
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
@ -40,7 +43,7 @@ actionstop = <iptables> -D <chain> -m set --match-set f2b-<name> src -j <blockty
# Tags: See jail.conf(5) man page # Tags: See jail.conf(5) man page
# Values: CMD # Values: CMD
# #
actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist actionban = ipset add <ipmset> <ip> timeout <bantime> -exist
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
@ -48,7 +51,7 @@ actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist
# Tags: See jail.conf(5) man page # Tags: See jail.conf(5) man page
# Values: CMD # Values: CMD
# #
actionunban = ipset del f2b-<name> <ip> -exist actionunban = ipset del <ipmset> <ip> -exist
[Init] [Init]
@ -57,3 +60,12 @@ actionunban = ipset del f2b-<name> <ip> -exist
# Values: [ NUM ] Default: 600 # Values: [ NUM ] Default: 600
# #
bantime = 600 bantime = 600
ipmset = f2b-<name>
familyopt =
[Init?family=inet6]
ipmset = f2b-<name>6
familyopt = <sp>family inet6

View File

@ -12,6 +12,9 @@
# #
# If you are running on an older kernel you make need to patch in external # If you are running on an older kernel you make need to patch in external
# modules. # modules.
#
# Modified: Alexander Koeppe <format_c@online.de>, Serg G. Brester <serg.brester@sebres.de>
# made config file IPv6 capable (see new section Init?family=inet6)
[INCLUDES] [INCLUDES]
@ -23,16 +26,16 @@ before = iptables-common.conf
# Notes.: command executed once at the start of Fail2Ban. # Notes.: command executed once at the start of Fail2Ban.
# Values: CMD # Values: CMD
# #
actionstart = ipset create f2b-<name> hash:ip timeout <bantime> actionstart = ipset create <ipmset> hash:ip timeout <bantime><familyopt>
<iptables> -I <chain> -p <protocol> -m multiport --dports <port> -m set --match-set f2b-<name> src -j <blocktype> <iptables> -I <chain> -p <protocol> -m multiport --dports <port> -m set --match-set <ipmset> src -j <blocktype>
# Option: actionstop # Option: actionstop
# Notes.: command executed once at the end of Fail2Ban # Notes.: command executed once at the end of Fail2Ban
# Values: CMD # Values: CMD
# #
actionstop = <iptables> -D <chain> -p <protocol> -m multiport --dports <port> -m set --match-set f2b-<name> src -j <blocktype> actionstop = <iptables> -D <chain> -p <protocol> -m multiport --dports <port> -m set --match-set <ipmset> src -j <blocktype>
ipset flush f2b-<name> ipset flush <ipmset>
ipset destroy f2b-<name> ipset destroy <ipmset>
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
@ -40,7 +43,7 @@ actionstop = <iptables> -D <chain> -p <protocol> -m multiport --dports <port> -m
# Tags: See jail.conf(5) man page # Tags: See jail.conf(5) man page
# Values: CMD # Values: CMD
# #
actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist actionban = ipset add <ipmset> <ip> timeout <bantime> -exist
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
@ -48,7 +51,7 @@ actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist
# Tags: See jail.conf(5) man page # Tags: See jail.conf(5) man page
# Values: CMD # Values: CMD
# #
actionunban = ipset del f2b-<name> <ip> -exist actionunban = ipset del <ipmset> <ip> -exist
[Init] [Init]
@ -57,3 +60,12 @@ actionunban = ipset del f2b-<name> <ip> -exist
# Values: [ NUM ] Default: 600 # Values: [ NUM ] Default: 600
# #
bantime = 600 bantime = 600
ipmset = f2b-<name>
familyopt =
[Init?family=inet6]
ipmset = f2b-<name>6
familyopt = <sp>family inet6

View File

@ -2,7 +2,8 @@
# #
# Author: Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> # Author: Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
# #
# # Modified: Alexander Koeppe <format_c@online.de>, Serg G. Brester <serg.brester@sebres.de>
# made config file IPv6 capable
[INCLUDES] [INCLUDES]
@ -22,30 +23,30 @@ before = iptables-common.conf
# iptables-persistent package). # iptables-persistent package).
# #
# Explanation of the rule below: # Explanation of the rule below:
# Check if any packets coming from an IP on the f2b-<name> # Check if any packets coming from an IP on the <iptname>
# list have been seen in the last 3600 seconds. If yes, update the # list have been seen in the last 3600 seconds. If yes, update the
# timestamp for this IP and drop the packet. If not, let the packet # timestamp for this IP and drop the packet. If not, let the packet
# through. # through.
# #
# Fail2ban inserts blacklisted hosts into the f2b-<name> list # Fail2ban inserts blacklisted hosts into the <iptname> list
# and removes them from the list after some time, according to its # and removes them from the list after some time, according to its
# own rules. The 3600 second timeout is independent and acts as a # own rules. The 3600 second timeout is independent and acts as a
# safeguard in case the fail2ban process dies unexpectedly. The # safeguard in case the fail2ban process dies unexpectedly. The
# shorter of the two timeouts actually matters. # shorter of the two timeouts actually matters.
actionstart = if [ `id -u` -eq 0 ];then <iptables> -I <chain> -m recent --update --seconds 3600 --name f2b-<name> -j <blocktype>;fi actionstart = if [ `id -u` -eq 0 ];then <iptables> -I <chain> -m recent --update --seconds 3600 --name <iptname> -j <blocktype>;fi
# Option: actionstop # Option: actionstop
# Notes.: command executed once at the end of Fail2Ban # Notes.: command executed once at the end of Fail2Ban
# Values: CMD # Values: CMD
# #
actionstop = echo / > /proc/net/xt_recent/f2b-<name> actionstop = echo / > /proc/net/xt_recent/<iptname>
if [ `id -u` -eq 0 ];then <iptables> -D <chain> -m recent --update --seconds 3600 --name f2b-<name> -j <blocktype>;fi if [ `id -u` -eq 0 ];then <iptables> -D <chain> -m recent --update --seconds 3600 --name <iptname> -j <blocktype>;fi
# Option: actioncheck # Option: actioncheck
# Notes.: command executed once before each actionban command # Notes.: command executed once before each actionban command
# Values: CMD # Values: CMD
# #
actioncheck = test -e /proc/net/xt_recent/f2b-<name> actioncheck = test -e /proc/net/xt_recent/<iptname>
# Option: actionban # Option: actionban
# Notes.: command executed when banning an IP. Take care that the # Notes.: command executed when banning an IP. Take care that the
@ -53,7 +54,7 @@ actioncheck = test -e /proc/net/xt_recent/f2b-<name>
# Tags: See jail.conf(5) man page # Tags: See jail.conf(5) man page
# Values: CMD # Values: CMD
# #
actionban = echo +<ip> > /proc/net/xt_recent/f2b-<name> actionban = echo +<ip> > /proc/net/xt_recent/<iptname>
# Option: actionunban # Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the # Notes.: command executed when unbanning an IP. Take care that the
@ -61,7 +62,12 @@ actionban = echo +<ip> > /proc/net/xt_recent/f2b-<name>
# Tags: See jail.conf(5) man page # Tags: See jail.conf(5) man page
# Values: CMD # Values: CMD
# #
actionunban = echo -<ip> > /proc/net/xt_recent/f2b-<name> actionunban = echo -<ip> > /proc/net/xt_recent/<iptname>
[Init] [Init]
iptname = f2b-<name>
[Init?family=inet6]
iptname = f2b-<name>6

View File

@ -25,6 +25,7 @@ __copyright__ = 'Copyright (c) 2007 Yaroslav Halchenko'
__license__ = 'GPL' __license__ = 'GPL'
import os import os
import re
import sys import sys
from ..helpers import getLogger from ..helpers import getLogger
@ -99,6 +100,8 @@ after = 1.conf
SECTION_NAME = "INCLUDES" SECTION_NAME = "INCLUDES"
CONDITIONAL_RE = re.compile(r"^(\w+)(\?.+)$")
if sys.version_info >= (3,2): if sys.version_info >= (3,2):
# overload constructor only for fancy new Python3's # overload constructor only for fancy new Python3's
def __init__(self, share_config=None, *args, **kwargs): def __init__(self, share_config=None, *args, **kwargs):
@ -225,7 +228,19 @@ after = 1.conf
# merge defaults and all sections to self: # merge defaults and all sections to self:
alld.update(cfg.get_defaults()) alld.update(cfg.get_defaults())
for n, s in cfg.get_sections().iteritems(): for n, s in cfg.get_sections().iteritems():
if isinstance(s, dict): curalls = alls
# conditional sections
cond = SafeConfigParserWithIncludes.CONDITIONAL_RE.match(n)
if cond:
n, cond = cond.groups()
s = s.copy()
try:
del(s['__name__'])
except KeyError:
pass
for k in s.keys():
v = s.pop(k)
s[k + cond] = v
s2 = alls.get(n) s2 = alls.get(n)
if isinstance(s2, dict): if isinstance(s2, dict):
# save previous known values, for possible using in local interpolations later: # save previous known values, for possible using in local interpolations later:
@ -238,8 +253,6 @@ after = 1.conf
s2.update(s) s2.update(s)
else: else:
alls[n] = s.copy() alls[n] = s.copy()
else:
alls[n] = s
return ret return ret
@ -254,9 +267,12 @@ after = 1.conf
def merge_section(self, section, options, pref='known/'): def merge_section(self, section, options, pref='known/'):
alls = self.get_sections() alls = self.get_sections()
if pref == '':
alls[section].update(options)
return
sk = {} sk = {}
for k, v in options.iteritems(): for k, v in options.iteritems():
if pref == '' or (not k.startswith(pref) and k != '__name__'): if not k.startswith(pref) and k != '__name__':
sk[pref+k] = v sk[pref+k] = v
alls[section].update(sk) alls[section].update(sk)

View File

@ -32,6 +32,7 @@ import time
from abc import ABCMeta from abc import ABCMeta
from collections import MutableMapping from collections import MutableMapping
from .ipdns import asip
from .utils import Utils from .utils import Utils
from ..helpers import getLogger from ..helpers import getLogger
@ -41,6 +42,11 @@ logSys = getLogger(__name__)
# Create a lock for running system commands # Create a lock for running system commands
_cmd_lock = threading.Lock() _cmd_lock = threading.Lock()
# Todo: make it configurable resp. automatically set, ex.: `[ -f /proc/net/if_inet6 ] && echo 'yes' || echo 'no'`:
allowed_ipv6 = True
# compiled RE for tag name (replacement name)
TAG_CRE = re.compile(r'<([^ <>]+)>')
class CallingMap(MutableMapping): class CallingMap(MutableMapping):
"""A Mapping type which returns the result of callable values. """A Mapping type which returns the result of callable values.
@ -256,14 +262,18 @@ class CommandAction(ActionBase):
Replace the tags in the action command with actions properties Replace the tags in the action command with actions properties
and executes the resulting command. and executes the resulting command.
""" """
if (self._properties and # check valid tags in properties (raises ValueError if self recursion, etc.):
not self.substituteRecursiveTags(self._properties)): if self._properties:
self._logSys.error( self.substituteRecursiveTags(self._properties)
"properties contain self referencing definitions " # common (resp. ipv4):
"and cannot be resolved") startCmd = self.replaceTag('<actionstart>', self._properties, conditional='family=inet4')
raise RuntimeError("Error starting action") res = self.executeCmd(startCmd, self.timeout)
startCmd = self.replaceTag(self.actionstart, self._properties) # start ipv6 actions if available:
if not self.executeCmd(startCmd, self.timeout): if allowed_ipv6:
startCmd6 = self.replaceTag('<actionstart>', self._properties, conditional='family=inet6')
if startCmd6 != startCmd:
res &= self.executeCmd(startCmd6, self.timeout)
if not res:
raise RuntimeError("Error starting action") raise RuntimeError("Error starting action")
@property @property
@ -289,7 +299,7 @@ class CommandAction(ActionBase):
Dictionary which includes information in relation to Dictionary which includes information in relation to
the ban. the ban.
""" """
if not self._processCmd(self.actionban, aInfo): if not self._processCmd('<actionban>', aInfo):
raise RuntimeError("Error banning %(ip)s" % aInfo) raise RuntimeError("Error banning %(ip)s" % aInfo)
@property @property
@ -315,7 +325,7 @@ class CommandAction(ActionBase):
Dictionary which includes information in relation to Dictionary which includes information in relation to
the ban. the ban.
""" """
if not self._processCmd(self.actionunban, aInfo): if not self._processCmd('<actionunban>', aInfo):
raise RuntimeError("Error unbanning %(ip)s" % aInfo) raise RuntimeError("Error unbanning %(ip)s" % aInfo)
@property @property
@ -350,12 +360,19 @@ class CommandAction(ActionBase):
Replaces the tags in the action command with actions properties Replaces the tags in the action command with actions properties
and executes the resulting command. and executes the resulting command.
""" """
stopCmd = self.replaceTag(self.actionstop, self._properties) # common (resp. ipv4):
if not self.executeCmd(stopCmd, self.timeout): stopCmd = self.replaceTag('<actionstop>', self._properties, conditional='family=inet4')
res = self.executeCmd(stopCmd, self.timeout)
# ipv6 actions if available:
if allowed_ipv6:
stopCmd6 = self.replaceTag('<actionstop>', self._properties, conditional='family=inet6')
if stopCmd6 != stopCmd:
res &= self.executeCmd(stopCmd6, self.timeout)
if not res:
raise RuntimeError("Error stopping action") raise RuntimeError("Error stopping action")
@classmethod @classmethod
def substituteRecursiveTags(cls, tags): def substituteRecursiveTags(cls, tags, conditional=''):
"""Sort out tag definitions within other tags. """Sort out tag definitions within other tags.
Since v.0.9.2 supports embedded interpolation (see test cases for examples). Since v.0.9.2 supports embedded interpolation (see test cases for examples).
@ -374,7 +391,7 @@ class CommandAction(ActionBase):
Dictionary of tags(keys) and their values, with tags Dictionary of tags(keys) and their values, with tags
within the values recursively replaced. within the values recursively replaced.
""" """
t = re.compile(r'<([^ <>]+)>') t = TAG_CRE
# repeat substitution while embedded-recursive (repFlag is True) # repeat substitution while embedded-recursive (repFlag is True)
while True: while True:
repFlag = False repFlag = False
@ -394,14 +411,21 @@ class CommandAction(ActionBase):
if found_tag == tag or found_tag in done: if found_tag == tag or found_tag in done:
# recursive definitions are bad # recursive definitions are bad
#logSys.log(5, 'recursion fail tag: %s value: %s' % (tag, value) ) #logSys.log(5, 'recursion fail tag: %s value: %s' % (tag, value) )
return False raise ValueError(
if found_tag in cls._escapedTags or not found_tag in tags: "properties contain self referencing definitions "
"and cannot be resolved, fail tag: %s value: %s" % (tag, value))
repl = None
if found_tag not in cls._escapedTags:
repl = tags.get(found_tag + '?' + conditional)
if repl is None:
repl = tags.get(found_tag)
if repl is None:
# Escaped or missing tags - just continue on searching after end of match # Escaped or missing tags - just continue on searching after end of match
# Missing tags are ok - cInfo can contain aInfo elements like <HOST> and valid shell # Missing tags are ok - cInfo can contain aInfo elements like <HOST> and valid shell
# constructs like <STDIN>. # constructs like <STDIN>.
m = t.search(value, m.end()) m = t.search(value, m.end())
continue continue
value = value.replace('<%s>' % found_tag , tags[found_tag]) value = value.replace('<%s>' % found_tag, repl)
#logSys.log(5, 'value now: %s' % value) #logSys.log(5, 'value now: %s' % value)
done.append(found_tag) done.append(found_tag)
m = t.search(value, m.start()) m = t.search(value, m.start())
@ -443,7 +467,7 @@ class CommandAction(ActionBase):
return value return value
@classmethod @classmethod
def replaceTag(cls, query, aInfo): def replaceTag(cls, query, aInfo, conditional=''):
"""Replaces tags in `query` with property values. """Replaces tags in `query` with property values.
Parameters Parameters
@ -459,7 +483,7 @@ class CommandAction(ActionBase):
`query` string with tags replaced. `query` string with tags replaced.
""" """
string = query string = query
aInfo = cls.substituteRecursiveTags(aInfo) aInfo = cls.substituteRecursiveTags(aInfo, conditional)
for tag in aInfo: for tag in aInfo:
if "<%s>" % tag in query: if "<%s>" % tag in query:
value = str(aInfo[tag]) # assure string value = str(aInfo[tag]) # assure string
@ -469,10 +493,10 @@ class CommandAction(ActionBase):
value = cls.escapeTag(value) value = cls.escapeTag(value)
string = string.replace('<' + tag + '>', value) string = string.replace('<' + tag + '>', value)
# New line # New line
string = string.replace("<br>", '\n') string = reduce(lambda s, kv: s.replace(*kv), (("<br>", '\n'), ("<sp>", " ")), string)
return string return string
def _processCmd(self, cmd, aInfo = None): def _processCmd(self, cmd, aInfo=None, conditional=''):
"""Executes a command with preliminary checks and substitutions. """Executes a command with preliminary checks and substitutions.
Before executing any commands, executes the "check" command first Before executing any commands, executes the "check" command first
@ -496,7 +520,17 @@ class CommandAction(ActionBase):
self._logSys.debug("Nothing to do") self._logSys.debug("Nothing to do")
return True return True
checkCmd = self.replaceTag(self.actioncheck, self._properties) if conditional == '':
conditional = 'family=inet4'
if allowed_ipv6:
try:
ip = aInfo["ip"]
if ip and asip(ip).isIPv6:
conditional = 'family=inet6'
except KeyError:
pass
checkCmd = self.replaceTag('<actioncheck>', self._properties, conditional=conditional)
if not self.executeCmd(checkCmd, self.timeout): if not self.executeCmd(checkCmd, self.timeout):
self._logSys.error( self._logSys.error(
"Invariant check failed. Trying to restore a sane environment") "Invariant check failed. Trying to restore a sane environment")
@ -506,15 +540,15 @@ class CommandAction(ActionBase):
self._logSys.critical("Unable to restore environment") self._logSys.critical("Unable to restore environment")
return False return False
# Replace static fields
realCmd = self.replaceTag(cmd, self._properties, conditional=conditional)
# Replace tags # Replace tags
if not aInfo is None: if aInfo is not None:
realCmd = self.replaceTag(cmd, aInfo) realCmd = self.replaceTag(realCmd, aInfo, conditional=conditional)
else: else:
realCmd = cmd realCmd = cmd
# Replace static fields
realCmd = self.replaceTag(realCmd, self._properties)
return self.executeCmd(realCmd, self.timeout) return self.executeCmd(realCmd, self.timeout)
@staticmethod @staticmethod

View File

@ -54,12 +54,17 @@ class CommandActionTest(LogCaptureTestCase):
'xyz': "890 <ABC>", 'xyz': "890 <ABC>",
} }
# Recursion is bad # Recursion is bad
self.assertFalse(CommandAction.substituteRecursiveTags({'A': '<A>'})) self.assertRaises(ValueError,
self.assertFalse(CommandAction.substituteRecursiveTags({'A': '<B>', 'B': '<A>'})) lambda: CommandAction.substituteRecursiveTags({'A': '<A>'}))
self.assertFalse(CommandAction.substituteRecursiveTags({'A': '<B>', 'B': '<C>', 'C': '<A>'})) self.assertRaises(ValueError,
lambda: CommandAction.substituteRecursiveTags({'A': '<B>', 'B': '<A>'}))
self.assertRaises(ValueError,
lambda: CommandAction.substituteRecursiveTags({'A': '<B>', 'B': '<C>', 'C': '<A>'}))
# Unresolveable substition # Unresolveable substition
self.assertFalse(CommandAction.substituteRecursiveTags({'A': 'to=<B> fromip=<IP>', 'C': '<B>', 'B': '<C>', 'D': ''})) self.assertRaises(ValueError,
self.assertFalse(CommandAction.substituteRecursiveTags({'failregex': 'to=<honeypot> fromip=<IP>', 'sweet': '<honeypot>', 'honeypot': '<sweet>', 'ignoreregex': ''})) lambda: CommandAction.substituteRecursiveTags({'A': 'to=<B> fromip=<IP>', 'C': '<B>', 'B': '<C>', 'D': ''}))
self.assertRaises(ValueError,
lambda: CommandAction.substituteRecursiveTags({'failregex': 'to=<honeypot> fromip=<IP>', 'sweet': '<honeypot>', 'honeypot': '<sweet>', 'ignoreregex': ''}))
# missing tags are ok # missing tags are ok
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C>'}), {'A': '<C>'}) self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C>'}), {'A': '<C>'})
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C> <D> <X>','X':'fun'}), {'A': '<C> <D> fun', 'X':'fun'}) self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C> <D> <X>','X':'fun'}), {'A': '<C> <D> fun', 'X':'fun'})
@ -127,6 +132,7 @@ class CommandActionTest(LogCaptureTestCase):
CallingMap(matches=lambda: str(10))), CallingMap(matches=lambda: str(10))),
"09 10 11") "09 10 11")
def testReplaceNoTag(self):
# As tag not present, therefore callable should not be called # As tag not present, therefore callable should not be called
# Will raise ValueError if it is # Will raise ValueError if it is
self.assertEqual( self.assertEqual(

View File

@ -1416,7 +1416,6 @@ class DNSUtilsNetworkTests(unittest.TestCase):
self.assertNotEqual(ip4[0], None) self.assertNotEqual(ip4[0], None)
self.assertTrue(ip4[0] is not None) self.assertTrue(ip4[0] is not None)
self.assertFalse(ip4[0] is None) self.assertFalse(ip4[0] is None)
self.assertLess(None, ip4[0])
self.assertLess(ip4[0], ip4[1]) self.assertLess(ip4[0], ip4[1])
self.assertLess(ip4[1], ip4[2]) self.assertLess(ip4[1], ip4[2])
self.assertEqual(sorted(reversed(ip4)), ip4) self.assertEqual(sorted(reversed(ip4)), ip4)
@ -1424,7 +1423,6 @@ class DNSUtilsNetworkTests(unittest.TestCase):
self.assertNotEqual(ip6[0], None) self.assertNotEqual(ip6[0], None)
self.assertTrue(ip6[0] is not None) self.assertTrue(ip6[0] is not None)
self.assertFalse(ip6[0] is None) self.assertFalse(ip6[0] is None)
self.assertLess(None, ip6[0])
self.assertLess(ip6[0], ip6[1]) self.assertLess(ip6[0], ip6[1])
self.assertLess(ip6[1], ip6[2]) self.assertLess(ip6[1], ip6[2])
self.assertEqual(sorted(reversed(ip6)), ip6) self.assertEqual(sorted(reversed(ip6)), ip6)