Merge branch '0.10' into 0.11

# Conflicts:
#	config/action.d/firewallcmd-ipset.conf
#	fail2ban/server/jail.py
#	fail2ban/tests/servertestcase.py
pull/2019/merge
sebres 2017-12-06 00:14:23 +01:00
commit 7e5d8f37fd
14 changed files with 130 additions and 77 deletions

View File

@ -49,6 +49,7 @@ ver. 0.11.0-dev-0 (2017/??/??) - development nightly edition
* `action.d/pf.conf`:
- fixed syntax error in achnor definition (documentation, see gh-1919);
- enclose ports in braces for multiport jails (see gh-1925);
* `action.d/firewallcmd-ipset.conf`: fixed create of set for ipv6 (missing `family inet6`, gh-1990)
* `filter.d/sshd.conf`: extended failregex for modes "extra"/"aggressive": now finds all possible (also future)
forms of "no matching (cipher|mac|MAC|compression method|key exchange method|host key type) found",
see "ssherr.c" for all possible SSH_ERR_..._ALG_MATCH errors (gh-1943, gh-1944);
@ -60,12 +61,21 @@ ver. 0.11.0-dev-0 (2017/??/??) - development nightly edition
(corresponds %H, but allows space if not zero-padded).
- %l - one- or two-digit number giving the hour of the day (12-11) on a 12-hour clock,
(corresponds %I, but allows space if not zero-padded).
* `filter.d/exim.conf`: added mode `aggressive` to ban flood resp. DDOS-similar failures (gh-1983);
* New Actions:
- `action.d/nginx-block-map.conf` - in order to ban not IP-related tickets via nginx (session blacklisting in
nginx-location with map-file);
### Enhancements
* jail.conf: extended with new parameter `mode` for the filters supporting it (gh-1988);
* action.d/pf.conf: extended with bulk-unban, command `actionflush` in order to flush all bans at once.
* Introduced new parameters for logging within fail2ban-server (gh-1980).
Usage `logtarget = target[facility=..., datetime=on|off, format="..."]`:
- `facility` - specify syslog facility (default `daemon`, see https://docs.python.org/2/library/logging.handlers.html#sysloghandler
for the list of facilities);
- `datetime` - add date-time to the message (default on, ignored if `format` specified);
- `format` - specify own format how it will be logged, for example for short-log into STDOUT:
`fail2ban-server -f --logtarget 'stdout[format="%(relativeCreated)5d | %(message)s"]' start`;
ver. 0.10.1 (2017/10/12) - succeeded-before-friday-the-13th

View File

@ -18,7 +18,7 @@ before = firewallcmd-common.conf
[Definition]
actionstart = ipset create <ipmset> hash:ip
actionstart = ipset create <ipmset> hash:ip<familyopt>
firewall-cmd --direct --add-rule <family> filter <chain> 0 -p <protocol> -m multiport --dports <port> -m set --match-set <ipmset> src -j <blocktype>
actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 -p <protocol> -m multiport --dports <port> -m set --match-set <ipmset> src -j <blocktype>
@ -41,10 +41,12 @@ actionunban = ipset del <ipmset> <ip> -exist
chain = INPUT_direct
ipmset = f2b-<name>
familyopt =
[Init?family=inet6]
ipmset = f2b-<name>6
familyopt = <sp>family inet6
# DEV NOTES:

View File

@ -24,6 +24,21 @@ failregex = ^%(pid)s %(host_info)ssender verify fail for <\S+>: (?:Unknown user|
^%(pid)s SMTP protocol error in "[^"]+(?:"+[^"]*(?="))*?" %(host_info)sAUTH command used when not advertised\s*$
^%(pid)s no MAIL in SMTP connection from (?:[^\[\( ]* )?(?:\(\S*\) )?%(host_info)sD=\d\S*s(?: C=\S*)?\s*$
^%(pid)s (?:[\w\-]+ )?SMTP connection from (?:[^\[\( ]* )?(?:\(\S*\) )?%(host_info)sclosed by DROP in ACL\s*$
<mdre-<mode>>
mdre-aggressive = ^%(pid)s no host name found for IP address <HOST>$
^%(pid)s no IP address found for host \S+ \(during SMTP connection from \[<HOST>\]\)$
mdre-normal =
# Parameter `mode` - `normal` or `aggressive`.
# Aggressive mode can be used to match flood and ddos-similar log-entries like:
# 'no host found for IP', 'no IP found for host'.
# Note this is not an authentication failures, so it may produce lots of false
# positives on misconfigured MTAs.
# Ex.:
# filter = exim[mode=aggressive]
mode = normal
ignoreregex =

View File

@ -36,10 +36,10 @@ ngx_limit_req_zones = [^"]+
# Use following full expression if you should range limit request to specified
# servers, requests, referrers etc. only :
#
# failregex = ^\s*\[error\] \d+#\d+: \*\d+ limiting requests, excess: [\d\.]+ by zone "(?:%(ngx_limit_req_zones)s)", client: <HOST>, server: \S*, request: "\S+ \S+ HTTP/\d+\.\d+", host: "\S+"(, referrer: "\S+")?\s*$
# failregex = ^\s*\[[a-z]+\] \d+#\d+: \*\d+ limiting requests, excess: [\d\.]+ by zone "(?:%(ngx_limit_req_zones)s)", client: <HOST>, server: \S*, request: "\S+ \S+ HTTP/\d+\.\d+", host: "\S+"(, referrer: "\S+")?\s*$
# Shortly, much faster and stable version of regexp:
failregex = ^\s*\[error\] \d+#\d+: \*\d+ limiting requests, excess: [\d\.]+ by zone "(?:%(ngx_limit_req_zones)s)", client: <HOST>,
failregex = ^\s*\[[a-z]+\] \d+#\d+: \*\d+ limiting requests, excess: [\d\.]+ by zone "(?:%(ngx_limit_req_zones)s)", client: <HOST>,
ignoreregex =

View File

@ -154,10 +154,13 @@ logencoding = auto
enabled = false
# "mode" defines the mode of the filter (see corresponding filter implementation for more info).
mode = normal
# "filter" defines the filter to use by the jail.
# By default jails have names matching their filter name
#
filter = %(__name__)s
filter = %(__name__)s[mode=%(mode)s]
#
@ -274,8 +277,7 @@ action = %(action_)s
# To use more aggressive sshd modes set filter parameter "mode" in jail.local:
# normal (default), ddos, extra or aggressive (combines all).
# See "tests/files/logs/sshd" or "filter.d/sshd.conf" for usage example and details.
mode = normal
filter = sshd[mode=%(mode)s]
#mode = normal
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
@ -573,7 +575,6 @@ backend = %(syslog_backend)s
[postfix]
# To use another modes set filter parameter "mode" in jail.local:
mode = more
filter = postfix[mode=%(mode)s]
port = smtp,465,submission
logpath = %(postfix_log)s
backend = %(postfix_backend)s
@ -599,8 +600,7 @@ backend = %(syslog_backend)s
# To use more aggressive modes set filter parameter "mode" in jail.local:
# normal (default), extra or aggressive
# See "tests/files/logs/sendmail-reject" or "filter.d/sendmail-reject.conf" for usage example and details.
mode = normal
filter = sendmail-reject[mode=%(mode)s]
#mode = normal
port = smtp,465,submission
logpath = %(syslog_mail)s
backend = %(syslog_backend)s
@ -636,7 +636,8 @@ logpath = %(solidpop3d_log)s
[exim]
# see filter.d/exim.conf for further modes supported from filter:
#mode = normal
port = smtp,465,submission
logpath = %(exim_main_log)s
@ -906,17 +907,14 @@ logpath = /var/log/haproxy.log
[slapd]
port = ldap,ldaps
filter = slapd
logpath = /var/log/slapd.log
[domino-smtp]
port = smtp,ssmtp
filter = domino-smtp
logpath = /home/domino01/data/IBM_TECHNICAL_SUPPORT/console.log
[phpmyadmin-syslog]
port = http,https
filter = phpmyadmin-syslog
logpath = %(syslog_authpriv)s
backend = %(syslog_backend)s

View File

@ -46,12 +46,12 @@ except ImportError:
FilterSystemd = None
from ..version import version
from .jailreader import JailReader
from .filterreader import FilterReader
from ..server.filter import Filter, FileContainer
from ..server.failregex import Regex, RegexException
from ..helpers import str2LogLevel, getVerbosityFormat, FormatterWithTraceBack, getLogger, PREFER_ENC
from ..helpers import str2LogLevel, getVerbosityFormat, FormatterWithTraceBack, getLogger, \
extractOptions, PREFER_ENC
# Gets the instance of the logger.
logSys = getLogger("fail2ban")
@ -287,7 +287,7 @@ class Fail2banRegex(object):
fltFile = None
fltOpt = {}
if regextype == 'fail':
fltName, fltOpt = JailReader.extractOptions(value)
fltName, fltOpt = extractOptions(value)
if fltName is not None:
if "." in fltName[~5:]:
tryNames = (fltName,)
@ -606,7 +606,7 @@ class Fail2banRegex(object):
return False
output( "Use systemd journal" )
output( "Use encoding : %s" % self._encoding )
backend, beArgs = JailReader.extractOptions(cmd_log)
backend, beArgs = extractOptions(cmd_log)
flt = FilterSystemd(None, **beArgs)
flt.setLogEncoding(self._encoding)
myjournal = flt.getJournalReader()

View File

@ -33,8 +33,7 @@ from .configreader import ConfigReaderUnshared, ConfigReader
from .filterreader import FilterReader
from .actionreader import ActionReader
from ..version import version
from ..helpers import getLogger
from ..helpers import splitwords
from ..helpers import getLogger, extractOptions, splitwords
# Gets the instance of the logger.
logSys = getLogger(__name__)
@ -42,15 +41,6 @@ logSys = getLogger(__name__)
class JailReader(ConfigReader):
# regex, to extract list of options:
optionCRE = re.compile(r"^([^\[]+)(?:\[(.*)\])?\s*$", re.DOTALL)
# regex, to iterate over single option in option list, syntax:
# `action = act[p1="...", p2='...', p3=...]`, where the p3=... not contains `,` or ']'
# since v0.10 separator extended with `]\s*[` for support of multiple option groups, syntax
# `action = act[p1=...][p2=...]`
optionExtractRE = re.compile(
r'([\w\-_\.]+)=(?:"([^"]*)"|\'([^\']*)\'|([^,\]]*))(?:,|\]\s*\[|$)', re.DOTALL)
def __init__(self, name, force_enable=False, **kwargs):
ConfigReader.__init__(self, **kwargs)
self.__name = name
@ -141,7 +131,7 @@ class JailReader(ConfigReader):
# Read filter
flt = self.__opts["filter"]
if flt:
filterName, filterOpt = JailReader.extractOptions(flt)
filterName, filterOpt = extractOptions(flt)
if not filterName:
raise JailDefError("Invalid filter definition %r" % flt)
self.__filter = FilterReader(
@ -171,7 +161,7 @@ class JailReader(ConfigReader):
try:
if not act: # skip empty actions
continue
actName, actOpt = JailReader.extractOptions(act)
actName, actOpt = extractOptions(act)
if not actName:
raise JailDefError("Invalid action definition %r" % act)
if actName.endswith(".py"):
@ -275,22 +265,5 @@ class JailReader(ConfigReader):
stream.insert(0, ["add", self.__name, backend])
return stream
@staticmethod
def extractOptions(option):
match = JailReader.optionCRE.match(option)
if not match:
# TODO proper error handling
return None, None
option_name, optstr = match.groups()
option_opts = dict()
if optstr:
for optmatch in JailReader.optionExtractRE.finditer(optstr):
opt = optmatch.group(1)
value = [
val for val in optmatch.group(2,3,4) if val is not None][0]
option_opts[opt.strip()] = value.strip()
return option_name, option_opts
class JailDefError(Exception):
pass

View File

@ -237,6 +237,34 @@ else:
return uni_decode(x, enc, 'replace')
#
# Following function used for parse options from parameter (e.g. `name[p1=0, p2="..."][p3='...']`).
#
# regex, to extract list of options:
OPTION_CRE = re.compile(r"^([^\[]+)(?:\[(.*)\])?\s*$", re.DOTALL)
# regex, to iterate over single option in option list, syntax:
# `action = act[p1="...", p2='...', p3=...]`, where the p3=... not contains `,` or ']'
# since v0.10 separator extended with `]\s*[` for support of multiple option groups, syntax
# `action = act[p1=...][p2=...]`
OPTION_EXTRACT_CRE = re.compile(
r'([\w\-_\.]+)=(?:"([^"]*)"|\'([^\']*)\'|([^,\]]*))(?:,|\]\s*\[|$)', re.DOTALL)
def extractOptions(option):
match = OPTION_CRE.match(option)
if not match:
# TODO proper error handling
return None, None
option_name, optstr = match.groups()
option_opts = dict()
if optstr:
for optmatch in OPTION_EXTRACT_CRE.finditer(optstr):
opt = optmatch.group(1)
value = [
val for val in optmatch.group(2,3,4) if val is not None][0]
option_opts[opt.strip()] = value.strip()
return option_name, option_opts
#
# Following facilities used for safe recursive interpolation of
# tags (<tag>) in tagged options.

View File

@ -29,8 +29,7 @@ import random
import Queue
from .actions import Actions
from ..client.jailreader import JailReader
from ..helpers import getLogger, MyTime
from ..helpers import getLogger, extractOptions, MyTime
from .mytime import MyTime
# Gets the instance of the logger.
@ -90,7 +89,7 @@ class Jail(object):
return "%s(%r)" % (self.__class__.__name__, self.name)
def _setBackend(self, backend):
backend, beArgs = JailReader.extractOptions(backend)
backend, beArgs = extractOptions(backend)
backend = backend.lower() # to assure consistent matching
backends = self._BACKENDS

View File

@ -38,7 +38,7 @@ from .filter import FileFilter, JournalFilter
from .transmitter import Transmitter
from .asyncserver import AsyncServer, AsyncServerException
from .. import version
from ..helpers import getLogger, str2LogLevel, getVerbosityFormat, excepthook
from ..helpers import getLogger, extractOptions, str2LogLevel, getVerbosityFormat, excepthook
# Gets the instance of the logger.
logSys = getLogger(__name__)
@ -577,6 +577,7 @@ class Server:
def setLogTarget(self, target):
# check reserved targets in uppercase, don't change target, because it can be file:
target, logOptions = extractOptions(target)
systarget = target.upper()
with self.__loggingLock:
# don't set new handlers if already the same
@ -589,7 +590,12 @@ class Server:
# set a format which is simpler for console use
fmt = "%(name)-24s[%(process)d]: %(levelname)-7s %(message)s"
if systarget == "SYSLOG":
facility = logging.handlers.SysLogHandler.LOG_DAEMON
facility = logOptions.get('facility', 'DAEMON').upper()
try:
facility = getattr(logging.handlers.SysLogHandler, 'LOG_' + facility)
except AttributeError: # pragma: no cover
logSys.error("Unable to set facility %r, using 'DAEMON'", logOptions.get('facility'))
facility = logging.handlers.SysLogHandler.LOG_DAEMON
if self.__syslogSocket == "auto":
import platform
self.__syslogSocket = self.__autoSyslogSocketPaths.get(
@ -640,9 +646,16 @@ class Server:
if self.__verbose is None:
self.__verbose = logging.DEBUG - logger.getEffectiveLevel() + 1
# If handler don't already add date to the message:
addtime = systarget not in ("SYSLOG", "SYSOUT")
addtime = logOptions.get('datetime')
if addtime is not None:
addtime = addtime in ('1', 'on', 'true', 'yes')
else:
addtime = systarget not in ("SYSLOG", "SYSOUT")
# If log-format is redefined in options:
if logOptions.get('format', '') != '':
fmt = logOptions.get('format')
# verbose log-format:
if self.__verbose is not None and self.__verbose > 2: # pragma: no cover
elif self.__verbose is not None and self.__verbose > 2: # pragma: no cover
fmt = getVerbosityFormat(self.__verbose-1,
addtime=addtime)
elif addtime:

View File

@ -30,7 +30,7 @@ import tempfile
import unittest
from ..client.configreader import ConfigReader, ConfigReaderUnshared, NoSectionError
from ..client import configparserinc
from ..client.jailreader import JailReader
from ..client.jailreader import JailReader, extractOptions
from ..client.filterreader import FilterReader
from ..client.jailsreader import JailsReader
from ..client.actionreader import ActionReader, CommandAction
@ -260,25 +260,25 @@ class JailReaderTest(LogCaptureTestCase):
# Simple example
option = "mail-whois[name=SSH]"
expected = ('mail-whois', {'name': 'SSH'})
result = JailReader.extractOptions(option)
result = extractOptions(option)
self.assertEqual(expected, result)
self.assertEqual(('mail.who_is', {}), JailReader.extractOptions("mail.who_is"))
self.assertEqual(('mail.who_is', {'a':'cat', 'b':'dog'}), JailReader.extractOptions("mail.who_is[a=cat,b=dog]"))
self.assertEqual(('mail--ho_is', {}), JailReader.extractOptions("mail--ho_is"))
self.assertEqual(('mail.who_is', {}), extractOptions("mail.who_is"))
self.assertEqual(('mail.who_is', {'a':'cat', 'b':'dog'}), extractOptions("mail.who_is[a=cat,b=dog]"))
self.assertEqual(('mail--ho_is', {}), extractOptions("mail--ho_is"))
self.assertEqual(('mail--ho_is', {}), JailReader.extractOptions("mail--ho_is['s']"))
self.assertEqual(('mail--ho_is', {}), extractOptions("mail--ho_is['s']"))
#self.printLog()
#self.assertLogged("Invalid argument ['s'] in ''s''")
self.assertEqual(('mail', {'a': ','}), JailReader.extractOptions("mail[a=',']"))
self.assertEqual(('mail', {'a': ','}), extractOptions("mail[a=',']"))
#self.assertRaises(ValueError, JailReader.extractOptions ,'mail-how[')
#self.assertRaises(ValueError, extractOptions ,'mail-how[')
# Empty option
option = "abc[]"
expected = ('abc', {})
result = JailReader.extractOptions(option)
result = extractOptions(option)
self.assertEqual(expected, result)
# More complex examples
@ -296,11 +296,11 @@ class JailReaderTest(LogCaptureTestCase):
'opt10': "",
'opt11': "",
})
result = JailReader.extractOptions(option)
result = extractOptions(option)
self.assertEqual(expected, result)
# And multiple groups (`][` instead of `,`)
result = JailReader.extractOptions(option.replace(',', ']['))
result = extractOptions(option.replace(',', ']['))
expected2 = (expected[0],
dict((k, v.replace(',', '][')) for k, v in expected[1].iteritems())
)
@ -439,7 +439,7 @@ class FilterReaderTest(unittest.TestCase):
def testFilterReaderSubstitionKnown(self):
output = [['set', 'jailname', 'addfailregex', 'to=test,sweet@example.com,test2,sweet@example.com fromip=<IP>']]
filterName, filterOpt = JailReader.extractOptions(
filterName, filterOpt = extractOptions(
'substition[honeypot="<sweet>,<known/honeypot>", sweet="test,<known/honeypot>,test2"]')
filterReader = FilterReader('substition', "jailname", filterOpt,
share_config=TEST_FILES_DIR_SHARE_CFG, basedir=TEST_FILES_DIR)
@ -650,7 +650,7 @@ class JailsReaderTest(LogCaptureTestCase):
if jail == 'INCLUDES':
continue
filterName = jails.get(jail, 'filter')
filterName, filterOpt = JailReader.extractOptions(filterName)
filterName, filterOpt = extractOptions(filterName)
allFilters.add(filterName)
self.assertTrue(len(filterName))
# moreover we must have a file for it
@ -669,7 +669,7 @@ class JailsReaderTest(LogCaptureTestCase):
# somewhat duplicating here what is done in JailsReader if
# the jail is enabled
for act in actions.split('\n'):
actName, actOpt = JailReader.extractOptions(act)
actName, actOpt = extractOptions(act)
self.assertTrue(len(actName))
self.assertTrue(isinstance(actOpt, dict))
if actName == 'iptables-multiport':
@ -696,7 +696,7 @@ class JailsReaderTest(LogCaptureTestCase):
if not (a.endswith('common.conf') or a.endswith('-aggressive.conf')))
# get filters of all jails (filter names without options inside filter[...])
filters_jail = set(
JailReader.extractOptions(jail.options['filter'])[0] for jail in jails.jails
extractOptions(jail.options['filter'])[0] for jail in jails.jails
)
self.maxDiff = None
self.assertTrue(filters.issubset(filters_jail),

View File

@ -201,7 +201,7 @@ def _start_params(tmp, use_stock=False, use_stock_cfg=None,
_write_file(pjoin(cfg, "fail2ban.conf"), "w",
"[Definition]",
"loglevel = INFO",
"logtarget = " + logtarget,
"logtarget = " + logtarget.replace('%', '%%'),
"syslogsocket = auto",
"socket = " + pjoin(tmp, "f2b.sock"),
"pidfile = " + pjoin(tmp, "f2b.pid"),
@ -767,7 +767,8 @@ class Fail2banServerTest(Fail2banClientServerBase):
def testKillAfterStart(self, tmp):
try:
# to prevent fork of test-cases process, start server in background via command:
startparams = _start_params(tmp, logtarget=pjoin(tmp, "f2b.log"))
startparams = _start_params(tmp, logtarget=pjoin(tmp,
'f2b.log[format="SRV: %(relativeCreated)3d | %(message)s", datetime=off]'))
# start (in new process, using the same python version):
cmd = (sys.executable, pjoin(BIN, SERVER))
logSys.debug('Start %s ...', cmd)

View File

@ -83,3 +83,17 @@
2017-11-28 14:14:31 SMTP protocol error in "aUtH lOgIn" H=(roxzgj) [192.0.2.5] AUTH command used when not advertised
# failJSON: { "time": "2017-11-28T14:14:32", "match": true , "host": "192.0.2.6", "desc": "quoted injecting on AUTH command" }
2017-11-28 14:14:32 SMTP protocol error in "aUtH lOgIn" H=(test) [8.8.8.8]" H=(roxzgj) [192.0.2.6] AUTH command used when not advertised
## no matches with `mode = normal`:
# failJSON: { "match": false , "desc": "aggressive mode only" }
2017-12-03 08:32:00 no host name found for IP address 192.0.2.8
# failJSON: { "match": false , "desc": "aggressive mode only" }
2017-12-03 08:51:35 no IP address found for host test.example.com (during SMTP connection from [192.0.2.9])
# filterOptions: [{"mode": "aggressive"}]
# failJSON: { "time": "2017-12-03T08:32:00", "match": true , "host": "192.0.2.8", "desc": "no host found for IP" }
2017-12-03 08:32:00 no host name found for IP address 192.0.2.8
# failJSON: { "time": "2017-12-03T08:51:35", "match": true , "host": "192.0.2.9", "desc": "no IP found for host" }
2017-12-03 08:51:35 no IP address found for host test.example.com (during SMTP connection from [192.0.2.9])

View File

@ -42,7 +42,7 @@ from ..server.ticket import BanTicket
from ..server.utils import Utils
from .dummyjail import DummyJail
from .utils import LogCaptureTestCase
from ..helpers import getLogger, PREFER_ENC
from ..helpers import getLogger, extractOptions, PREFER_ENC
from .. import version
try:
@ -831,8 +831,8 @@ class TransmitterLogging(TransmitterBase):
for logTarget in logTargets:
os.remove(logTarget)
self.setGetTest("logtarget", "STDOUT")
self.setGetTest("logtarget", "STDERR")
self.setGetTest("logtarget", 'STDOUT[format="%(message)s"]', 'STDOUT')
self.setGetTest("logtarget", 'STDERR[datetime=off]', 'STDERR')
def testLogTargetSYSLOG(self):
if not os.path.exists("/dev/log"):
@ -1043,7 +1043,7 @@ class LoggingTests(LogCaptureTestCase):
os.remove(f)
from clientreadertestcase import ActionReader, JailReader, JailsReader, CONFIG_DIR, STOCK
from clientreadertestcase import ActionReader, JailsReader, CONFIG_DIR, STOCK
class ServerConfigReaderTests(LogCaptureTestCase):
@ -1166,7 +1166,7 @@ class ServerConfigReaderTests(LogCaptureTestCase):
def getDefaultJailStream(self, jail, act):
act = act.replace('%(__name__)s', jail)
actName, actOpt = JailReader.extractOptions(act)
actName, actOpt = extractOptions(act)
stream = [
['add', jail, 'polling'],
# ['set', jail, 'addfailregex', 'DUMMY-REGEX <HOST>'],
@ -1674,7 +1674,7 @@ class ServerConfigReaderTests(LogCaptureTestCase):
"`firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset src -j REJECT --reject-with icmp-port-unreachable`",
),
'ip6-start': (
"`ipset create f2b-j-w-fwcmd-ipset6 hash:ip`",
"`ipset create f2b-j-w-fwcmd-ipset6 hash:ip family inet6`",
"`firewall-cmd --direct --add-rule ipv6 filter INPUT_direct 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`",
),
'stop': (