Extensibility¶
linuxnet.iptables supports only a subset of the match extensions and rule targets that are available in the Linux kernel.
Additional matches can be supported by subclassing the
Match and Criterion classes.
Additional targets can be supported by subclassing the
Target class.
Supporting a new match¶
Supporting a new iptables match requires the addition of a new
Match subclasss and one or more Criterion
subclasses. This will be illustrated via example; the example
uses the implementation of the existing TcpmssMatch class which
supports the iptables tcpmss match extension:
iptables -m tcpmss --mss value
The example code includes the TcpmssMatch class and the
associated MssCriterion class.
All classes needed for implementing a new match are available in the linux.iptables.extension module.
1# Copyright (c) 2021-2023, Panagiotis Tsirigotis
2
3"""
4Example module implementing matching against the TCP MSS
5"""
6
7from typing import List, Optional, Tuple
8
9from linuxnet.iptables.extension import (
10 Match,
11 Criterion,
12 MatchParser,
13 IptablesError)
14
15class TcpmssMatch(Match):
16 """Match against the MSS field of the TCP header
17 """
18 def __init__(self):
19 """This match uses a single criterion of type MssCritetion defined
20 below
21 """
22 self.__mss_crit = None
23
24 @staticmethod
25 def get_match_name():
26 """Returns the match extension name
27 """
28 return 'tcpmss'
29
30 def get_criteria(self):
31 """Returns the tcpmss criteria.
32 """
33 return (self.__mss_crit,)
34
35 def mss(self) -> 'MssCriterion':
36 """Match against the MSS field of the TCP header
37 """
38 if self.__mss_crit is None:
39 self.__mss_crit = MssCriterion(self)
40 return self.__mss_crit
41
42 @classmethod
43 def parse(cls, parser: MatchParser) -> Match:
44 """Parse tcpmss; example::
45
46 tcpmss match !10:20
47
48 The 'tcpmss' field has already been consumed by the parser.
49 """
50 parser.skip_field('match')
51 criteria_iter = parser.get_iter()
52 is_equal, val = parser.parse_value(next(criteria_iter))
53 if ':' in val:
54 mstr, estr = val.split(':', 1)
55 mssval = int(mstr)
56 endval = int(estr)
57 else:
58 mssval = int(val)
59 endval = None
60 return TcpmssMatch().mss().compare(is_equal, mssval, endval)
61
62# Registration of class, executed upon module import
63MatchParser.register_match('tcpmss', TcpmssMatch)
64
65
66class MssCriterion(Criterion):
67 """Compare with MSS field of the TCP header.
68
69 The comparison value is a tuple (int, int|None) to compare against
70 a specific MSS value or a range of values.
71 """
72 def __init__(self, match):
73 super().__init__(match)
74 self.__mssval = None
75 self.__endval = None
76
77 def get_value(self) -> Tuple[int, Optional[int]]:
78 """Returns the value that the criterion is comparing against.
79
80 :rtype: tuple of (int, int|None)
81 """
82 return (self.__mssval, self.__endval)
83
84 def equals(self, mssval: int, endval: Optional[int] =None) -> Match:
85 """Check for equality against ``mssval``, or range equality
86 if ``endval`` is present.
87
88 :param mssval: the MSS value
89 :param endval: range of MSS values from ``mssval`` to this value
90 """
91 self.__mssval = mssval
92 if endval is not None and endval < mssval:
93 raise IptablesError('bad range: endval < mssval')
94 self.__endval = endval
95 return self._set_polarity(True)
96
97 def _crit_iptables_args(self) -> List[str]:
98 """Returns **iptables(8)** arguments for the criterion
99 """
100 mss = f'{self.__mssval}'
101 if self.__endval is not None:
102 mss += f':{self.__endval}'
103 return ['--mss', mss]
Adding a Match subclass¶
A new Match subclass must implement the following methods:
get_match_name(): this method returns the iptables(8) match extension nameget_criteria(): this method returns an iterable with the new match’s criteriaparse(): this is a classmethod that takes a single argument of typeMatchParserand returns an instance of the new subclass. This method is responsible for parsing theiptables -Lxnvoutput for this match. Notice the use of the-Lxnvoptions when implementing this method.This method should raise an
IptablesParsingErrorif unable to parse the iptables output.This method may raise a
CriteriaExhaustedErrorto terminate the match parsing process.One or more methods returning instances of the match-specific criteria. In the example, the relevant method is
TcpmssMatch.mss()(theTcpmssMatchsupports a single criterion) which returns an instance of theMssCriterionclass.
Finally, the new subclass must be registered with the MatchParser
class by invoking the method MatchParser.register_class(), and specifying
the keyword in the iptables(8) output that identifies the particular
match (in this example, that keyword is tcpmss)
Adding a Criterion subclass¶
A new Criterion subclass must implement the following methods:
equals(): this method expresses an equality comparison against a specific value. The method stores this value to be used later to generate the iptables arguments. In our example, theMssCriterioncomparison value is an integer or an integer range.Notice the invocation of
Match._set_polarity()at the end of this method; this is required and it serves two purposes:it marks the criterion as having been set
it identifies that the criterion expresses an equality comparison (i.e. if the polarity were set to
False, the criterion would express an inequality comparison)
a method that returns the iptables arguments for implementing this criterion. In our example this method is
MssCriterion._crit_iptables_args(); it returns a list with the relevant iptables option and the stored value, e.g.['--mss', '500']. This method is invoked by theTcpmssMatch.to_iptables_args()method.
Supporting a new target¶
Supporting a new iptables target extension requires the addition of a new
Target subclasss. This will be illustrated via example; the example
uses the implementation of the existing RejectTarget class which
supports the iptables REJECT target extension.
All classes needed for implementing a new target are available in the linux.iptables.extension module.
1# Copyright (c) 2021-2024, Panagiotis Tsirigotis
2
3"""
4Example module implementing a class to support the REJECT target
5"""
6
7from typing import List, Optional
8from linuxnet.iptables.extension import Target, TargetParser
9
10class RejectTarget(Target):
11 """This class provides access to the ``REJECT`` target
12 """
13 def __init__(self, reject_with: Optional[str] =None):
14 """
15 :param reject_with: optional ``ICMP`` message type
16 """
17 super().__init__('REJECT', terminates=True)
18 self.__reject_with = reject_with
19
20 def to_iptables_args(self) -> List[str]:
21 """Returns a list of **iptables(8)** arguments
22 """
23 retval = super().to_iptables_args()
24 if self.__reject_with is not None:
25 retval += ['--reject-with', self.__reject_with]
26 return retval
27
28 @classmethod
29 def parse(cls, parser: TargetParser) -> Target:
30 """Parse the REJECT target options
31 """
32 field_iter = parser.get_field_iter()
33 icmp_message = field_iter.next_value('reject-with')
34 return RejectTarget(reject_with=icmp_message)
35
36TargetParser.register_target('REJECT', RejectTarget, 'reject-with')
A new Target subclass must implement the following methods:
to_iptables_args(): this method should return the iptables(8) arguments for the target. In our example, the return value might look like this:['-j', 'REJECT', '--reject-with', 'icmp-port-unreachable']
assuming the
RejectTargetobject was initialized with the particular ICMP message.parse(): this is a classmethod that takes a single argument of typeTargetParserand returns an instance of the new subclass.This method should raise an
IptablesParsingErrorif unable to parse the iptables output.
The new subclass must be registered with the
TargetParser class by invoking the method
TargetParser.register_target(), and specifying
the target name.
If there is a field in the iptables output
that identifies the beginning of the target options, this field
can be passed as an argument to the register_target() method.
In our example, this was the reject-with field.
Specifying such a field is optional. If specified, it
allows the field iterator inside the
TargetParser instance, which is passed as an argument to the
parse() method, to be positioned right after that field
(the iterator is accessible via the
TargetParser.get_field_iterator() method).
Check the documentation of TargetParser.register_target()
for possible arguments to this method.
Base classes¶
Match¶
The Match class is the parent class of all match-related classes.
Criterion¶
The Criterion class is the parent class of all classes implementing
match-specific criteria.
Objects of subclasses of Criterion are never directly instantiated
by the user; they are instantiated
as needed by the Match subclasses.
- class Criterion(match: Match)[source]¶
This class is used to express an iptables(8) match criterion; it does not perform any comparisons.
Criterionis a superclass that serves the following purposes:it provides an
equals()method and anot_equals()method to express a==or a!=comparison against a valueit keeps track of whether the criterion has been set; a criterion is set when either the
equals()ornot_equals()method is invoked; a criterion may only be set onceit keeps track of whether a criterion is negated or not; this is the criterion’s polarity (a criterion that performs a
!=comparison has negative polarity)it provides a
to_iptables_args()method to generate the!(negation) iptables(8) argument, and to also check if the criterion was set
The
equals()/not_equals()methods ofCriterionsubclasses must invoke the_set_polarity()method ofCriterionto indicate the polarity of the test. These methods are also responsible for saving the comparison value in the subclass object.A
Criterionhas an owner which is an object of a subclass ofMatch. Theequals()/not_equals()methods return this object to facilitate building a criteria list:pkt_match.protocol().equals('tcp').input_interface().equals('eth0')
- Parameters:
match – the
Matchobject that owns thisCriterion
- is_positive() bool[source]¶
Returns the ‘polarity’ of the criterion:
Trueforequals()orFalsefornot_equals()Raises
IptablesErrorif the criterion is not set
- any() Match[source]¶
Match any value.
This method is used when creating a
Criterionin order to search an existing chain for rules that try to match against certain packet properties (e.g. input interface) without being particular about the specific property value (e.g.eth0).
- equals(*args, **kwargs) Match[source]¶
Express equality comparison against the argument values.
Subclasses will implement this method to express comparisons against a specific value (or values). These values will be the arguments of the subclass method and will be stored in the subclass object.
Subclasses overriding this method should invoke the
_set_polarity()method of this class to set the polarity toTrue.Returns this
Matchobject.
- not_equals(*args, **kwargs) Match[source]¶
Express inequality comparison against the argument values.
The arguments of this method are the same as those of the
equals()method.This method invokes the
equals()method and then reverses the polarity.Returns this
Matchobject.
- compare(is_equal: bool, *args, **kwargs) Match[source]¶
Alternative method used for comparisons. It invokes
equals()(ornot_equals()) withargsandkwargsifis_equalisTrue(orFalse).
- _crit_iptables_args() List[str][source]¶
Returns a list of iptables(8) arguments for the criterion, except for polarity.
Subclasses must implement this method.
- to_iptables_args() List[str][source]¶
Returns a list of iptables(8) arguments
This method should be invoked only for criteria that are set, i.e. the caller is expected to check with
Criterion.is_set()prior to invoking this method.
Target¶
Parser classes¶
MatchParser¶
A MatchParser instance is used to parse the match part of
a rule in the iptables(8) output.
- class MatchParser(field_iter: LookaheadIterator, *, ipv6: bool)[source]¶
This class handles match parsing
- Parameters:
field_iter – a
RuleFieldIteratorthat iterates over the fields of the ruleipv6 – if
True, then we are parsing the output of ip6tables(8)
- get_iter() LookaheadIterator[source]¶
Returns the field iterator
- static parse_value(value: str) Tuple[bool, str][source]¶
Check if the specified value starts with ‘!’ indicating negation. Returns the tuple (is_negative, value) where the optional ‘!’ has been stripped from the argument ‘value’
- parse_next_value() Tuple[bool, str][source]¶
Parse the next value from the iterator. Allow for the following syntax:
! value (2 fields) !value (1 field)
Returns the tuple (is_negative, value)
- skip_field(expected: str)[source]¶
Skip the next field, if it is equal to
expected. Otherwise, raise anIptablesParsingErrorexception.
LookaheadIterator¶
- class LookaheadIterator(iterable, lookahead: int)[source]¶
A LookaheadIterator is an iterator that provides the ability to put back previously returned tokens.
Conceptual view of the LookaheadIterator:
deque +---------------+ +---+---+---+---+---+ | back-iterator | | T | T | T |...| T | +---------------+ +---+---+---+---+---+ ^ | Cursor
Tokens to the right of the cursor have been consumed.
Tokens up to, but not including, the cursor are previously consumed tokens that have been put back.
New tokens are obtained from the back-iterator.
The value of the cursor indicates the number of put-back tokens.
The maximum size of the deque is equal to the lookahead.
- Parameters:
iterable – an iterable object from which we create the back-iterator
lookahead – number of tokens of look ahead
- put_back(token: str, *, replace_token=False) None[source]¶
This method puts the previously returned token back to the iterator, so that the token can be returned again.
- Parameters:
token – the token to put back to the iterator; when
replace_tokenisFalse, this must be the token previously returned by the iterator: identity is checked, not equalityreplace_token – if
True, it allowstokento be different than then one previously returned by the iterator
- rewind(step=1) LookaheadIterator[source]¶
Put back last
steptokensA
ValueErrorwill be raised if there are not enough tokens to put back.
TargetParser¶
A TargetParser instance is used to parse the target part of
a rule in the iptables(8) output.
- class TargetParser(target_name: Optional[str], field_iter: RuleFieldIterator, *, ipv6: bool)[source]¶
This class handles target parsing
- Parameters:
target_name – the target name
field_iter – a
RuleFieldIteratoripv6 – if
True, then we are parsing the output of ip6tables(8)
- get_field_iter() RuleFieldIterator[source]¶
Returns the
RuleFieldIteratorinstance that iterates over the fields of the rule.
- classmethod register_target(target_name: str, target_klass, start_field: Optional[Union[str, Tuple]] = None, prefix_match: Optional[bool] = False)[source]¶
Register a class to handle parsing for a target.
- Parameters:
target_name – this is the target name that appears in the
iptables -Loutput.target_klass – a subclass of
Targetstart_field – the field in the iptables(8) output that is the beginning of the target fields; if present, the iterator passed to
parse()of thetarget_classwill be forwarded past the field that matchesstart_field;start_fieldmay also be specified as tuple of field namesprefix_match – if
True,start_fieldis the prefix of the field that is the beginning of the target fields
RuleFieldIterator¶
- class RuleFieldIterator(*args, **kwargs)[source]¶
Bases:
LookaheadIteratorThis iterator is used to parse the target-related fields of a rule. When instantiated, the iterator may still be ‘inside’ the match fields due to encountering an unsupported match.
The target-specific parsing code should advance the iterator until it finds the beginning of the target-related fields. The methods
forward()andforward_to()may be used for this.- Parameters:
iterable – an iterable object from which we create the back-iterator
lookahead – number of tokens of look ahead
- next_field(attr: Optional[str] = None) str[source]¶
Returns the next field which holds the value for
attrRaises an
IptablesParsingErrorif the iterator has run out of fields.
- forward(field_name: Optional[Union[str, Tuple]], *, prefix_match: Optional[bool] = False) RuleFieldIterator[source]¶
Forward past the field identified by
field_name, i.e. the next field to be returned by the iterator will be the one afterfield_name.- Parameters:
field_name – the name of field to reach; if
None, this method call is a no-opprefix_match – if
True, match the first field with afield_nameprefix
- Return type:
this object
Raises an
IptablesParsingErrorif no match is found.
- forward_to(fields: Iterable[str]) Optional[str][source]¶
Forward to a field among those in
fields; this field will be returned, if found. The next field returned by the iterator will be the one found.- Parameters:
fields – field to match; if
Noneor empty, this method call is a no-op- Return type:
the matching field, or
None
- store_field(field: str) None[source]¶
Store the specified field.
Target subclasses that choose to do their own forwarding to find the field that starts the target options (instead of using the
forward()method) should invoke this method to ‘store’ the fields they skip.
- peek() Optional[str]¶
Returns the next token, but does not consume it
- put_back(token: str, *, replace_token=False) None¶
This method puts the previously returned token back to the iterator, so that the token can be returned again.
- Parameters:
token – the token to put back to the iterator; when
replace_tokenisFalse, this must be the token previously returned by the iterator: identity is checked, not equalityreplace_token – if
True, it allowstokento be different than then one previously returned by the iterator
- rewind(step=1) LookaheadIterator¶
Put back last
steptokensA
ValueErrorwill be raised if there are not enough tokens to put back.
Utility classes¶
BooleanCriterion¶
- class BooleanCriterion(match: Match, iptables_option: str, supports_negation=True)[source]¶
Bases:
CriterionHelper class for criteria that test single bits.
- Parameters:
match – the
Matchobject that owns thisCriterioniptables_option – the iptables(8) option to use for this criterion
supports_negation – if
True, the criteria supports the meth:not_equals method
GenericCriterion¶
- class GenericCriterion(match: Match, iptables_option: str, norm=None)[source]¶
Bases:
CriterionA helper class that can be used by all criteria that correspond to iptables(8) options of the form “[!] option value”, for example, “[!] -p tcp”
- Parameters:
match – the owner
Matchiptables_option – the iptables(8) option to use when generating the iptables arguments
norm – an optional callable that normalizes the value that we are comparing against
GenericPositiveCriterion¶
- class GenericPositiveCriterion(match: Match, iptables_option: str, norm=None)[source]¶
Bases:
GenericCriterionA subclass of
GenericCriterionfor criteria that do not support negation.- Parameters:
match – the owner
Matchiptables_option – the iptables(8) option to use when generating the iptables arguments
norm – an optional callable that normalizes the value that we are comparing against