summaryrefslogtreecommitdiff
path: root/lib/iptables.py
diff options
context:
space:
mode:
authorJohan Lundberg <lundberg@nordu.net>2015-04-02 10:43:33 +0200
committerJohan Lundberg <lundberg@nordu.net>2015-04-02 10:43:33 +0200
commitbd611ac59f7c4db885a2f8631ef0bcdcd1901ca0 (patch)
treee60f5333a7699cd021b33c7f5292af55b774001b /lib/iptables.py
Diffstat (limited to 'lib/iptables.py')
-rw-r--r--lib/iptables.py789
1 files changed, 789 insertions, 0 deletions
diff --git a/lib/iptables.py b/lib/iptables.py
new file mode 100644
index 0000000..d465c74
--- /dev/null
+++ b/lib/iptables.py
@@ -0,0 +1,789 @@
+#!/usr/bin/python
+#
+# Copyright 2010 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""Iptables generator."""
+
+__author__ = 'watson@google.com (Tony Watson)'
+
+import datetime
+import logging
+import nacaddr
+import re
+from string import Template
+
+import aclgenerator
+
+
+class Term(aclgenerator.Term):
+ """Generate Iptables policy terms."""
+
+ # Validate that term does not contain any fields we do not
+ # support. This prevents us from thinking that our output is
+ # correct in cases where we've omitted fields from term.
+ _PLATFORM = 'iptables'
+ _POSTJUMP_FORMAT = None
+ _PREJUMP_FORMAT = Template('-A $filter -j $term')
+ _TERM_FORMAT = Template('-N $term')
+ _COMMENT_FORMAT = Template('-A $term -m comment --comment "$comment"')
+ _FILTER_TOP_FORMAT = Template('-A $term')
+ _ACTION_TABLE = {
+ 'accept': '-j ACCEPT',
+ 'deny': '-j DROP',
+ 'reject': '-j REJECT --reject-with icmp-host-prohibited',
+ 'reject-with-tcp-rst': '-j REJECT --reject-with tcp-reset',
+ 'next': '-j RETURN'
+ }
+ _PROTO_TABLE = {
+ 'icmpv6': '-p icmpv6',
+ 'icmp': '-p icmp',
+ 'tcp': '-p tcp',
+ 'udp': '-p udp',
+ 'all': '-p all',
+ 'esp': '-p esp',
+ 'ah': '-p ah',
+ 'gre': '-p gre',
+ }
+ _TCP_FLAGS_TABLE = {
+ 'syn': 'SYN',
+ 'ack': 'ACK',
+ 'fin': 'FIN',
+ 'rst': 'RST',
+ 'urg': 'URG',
+ 'psh': 'PSH',
+ 'all': 'ALL',
+ 'none': 'NONE',
+ }
+ _KNOWN_OPTIONS_MATCHERS = {
+ # '! -f' also matches non-fragmented packets.
+ 'first-fragment': '-m u32 --u32 4&0x3FFF=0x2000',
+ 'initial': '--syn',
+ 'tcp-initial': '--syn',
+ 'sample': '',
+ }
+
+ def __init__(self, term, filter_name, trackstate, filter_action, af='inet'):
+ """Setup a new term.
+
+ Args:
+ term: A policy.Term object to represent in iptables.
+ filter_name: The name of the filter chan to attach the term to.
+ trackstate: Specifies if conntrack should be used for new connections
+ filter_action: The default action of the filter.
+ af: Which address family ('inet' or 'inet6') to apply the term to.
+
+ Raises:
+ UnsupportedFilterError: Filter is not supported.
+ """
+ self.trackstate = trackstate
+ self.term = term # term object
+ self.filter = filter_name # actual name of filter
+ self.default_action = filter_action
+ self.options = []
+ self.af = af
+
+ if af == 'inet6':
+ self._all_ips = nacaddr.IPv6('::/0')
+ self._ACTION_TABLE['reject'] = '-j REJECT --reject-with adm-prohibited'
+ else:
+ self._all_ips = nacaddr.IPv4('0.0.0.0/0')
+ self._ACTION_TABLE['reject'] = ('-j REJECT --reject-with '
+ 'icmp-host-prohibited')
+
+ self.term_name = '%s_%s' % (self.filter[:1], self.term.name)
+
+ def __str__(self):
+ # Verify platform specific terms. Skip whole term if platform does not
+ # match.
+ if self.term.platform:
+ if self._PLATFORM not in self.term.platform:
+ return ''
+ if self.term.platform_exclude:
+ if self._PLATFORM in self.term.platform_exclude:
+ return ''
+
+ ret_str = []
+
+ # Don't render icmpv6 protocol terms under inet, or icmp under inet6
+ if ((self.af == 'inet6' and 'icmp' in self.term.protocol) or
+ (self.af == 'inet' and 'icmpv6' in self.term.protocol)):
+ ret_str.append('# Term %s' % self.term.name)
+ ret_str.append('# not rendered due to protocol/AF mismatch.')
+ return '\n'.join(ret_str)
+
+ # Term verbatim output - this will skip over most normal term
+ # creation code by returning early. Warnings provided in policy.py
+ if self.term.verbatim:
+ for next_verbatim in self.term.verbatim:
+ if next_verbatim.value[0] == self._PLATFORM:
+ ret_str.append(str(next_verbatim.value[1]))
+ return '\n'.join(ret_str)
+
+ # We don't support these keywords for filtering, so unless users
+ # put in a "verbatim:: iptables" statement, any output we emitted
+ # would misleadingly suggest that we applied their filters.
+ # Instead, we fail loudly.
+ if self.term.ether_type:
+ raise UnsupportedFilterError('\n%s %s %s %s' % (
+ 'ether_type unsupported by', self._PLATFORM,
+ '\nError in term', self.term.name))
+ if self.term.address:
+ raise UnsupportedFilterError('\n%s %s %s %s %s' % (
+ 'address unsupported by', self._PLATFORM,
+ '- specify source or dest', '\nError in term:', self.term.name))
+ if self.term.port:
+ raise UnsupportedFilterError('\n%s %s %s %s %s' % (
+ 'port unsupported by', self._PLATFORM,
+ '- specify source or dest', '\nError in term:', self.term.name))
+
+ # Create a new term
+ if self._TERM_FORMAT:
+ ret_str.append(self._TERM_FORMAT.substitute(term=self.term_name))
+
+ if self._PREJUMP_FORMAT:
+ ret_str.append(self._PREJUMP_FORMAT.substitute(filter=self.filter,
+ term=self.term_name))
+
+ if self.term.owner:
+ self.term.comment.append('Owner: %s' % self.term.owner)
+ # reformat long comments, if needed
+ #
+ # iptables allows individual comments up to 256 chars.
+ # But our generator will limit a single comment line to < 120, using:
+ # max = 119 - 27 (static chars in comment command) - [length of term name]
+ comment_max_width = 92 - len(self.term_name)
+ if comment_max_width < 40:
+ comment_max_width = 40
+ comments = aclgenerator.WrapWords(self.term.comment, comment_max_width)
+ # append comments to output
+ if comments and comments[0]:
+ for line in comments:
+ if not line:
+ continue # iptables-restore does not like 0-length comments.
+ # term comments
+ ret_str.append(self._COMMENT_FORMAT.substitute(filter=self.filter,
+ term=self.term_name,
+ comment=str(line)))
+
+ # if terms does not specify action, use filter default action
+ if not self.term.action:
+ self.term.action[0].value = self.default_action
+
+ # Unsupported configuration; in the case of 'accept' or 'next', we
+ # skip the rule. In other cases, we blow up (raise an exception)
+ # to ensure that this is not considered valid configuration.
+ if self.term.source_prefix or self.term.destination_prefix:
+ if str(self.term.action[0]) not in set(['accept', 'next']):
+ raise UnsupportedFilterError('%s %s %s %s %s %s %s %s' % (
+ '\nTerm', self.term.name, 'has action', str(self.term.action[0]),
+ 'with source_prefix or destination_prefix,',
+ ' which is unsupported in', self._PLATFORM, 'iptables output.'))
+ return ('# skipped %s due to source or destination prefix rule' %
+ self.term.name)
+
+ # protocol
+ if self.term.protocol:
+ protocol = self.term.protocol
+ else:
+ protocol = ['all']
+ if self.term.protocol_except:
+ raise UnsupportedFilterError('%s %s %s' % (
+ '\n', self.term.name,
+ 'protocol_except logic not currently supported.'))
+
+ (term_saddr, exclude_saddr,
+ term_daddr, exclude_daddr) = self._CalculateAddresses(
+ self.term.source_address, self.term.source_address_exclude,
+ self.term.destination_address, self.term.destination_address_exclude)
+ if not term_saddr:
+ logging.warn(self.NO_AF_LOG_FORMAT.substitute(term=self.term.name,
+ direction='source',
+ af=self.af))
+ return ''
+ if not term_daddr:
+ logging.warn(self.NO_AF_LOG_FORMAT.substitute(term=self.term.name,
+ direction='destination',
+ af=self.af))
+ return ''
+
+ # ports
+ source_port = []
+ destination_port = []
+ if self.term.source_port:
+ source_port = self.term.source_port
+ if self.term.destination_port:
+ destination_port = self.term.destination_port
+
+ # icmp-types
+ icmp_types = ['']
+ if self.term.icmp_type:
+ icmp_types = self.NormalizeIcmpTypes(self.term.icmp_type, protocol,
+ self.af)
+
+ source_interface = ''
+ if self.term.source_interface:
+ source_interface = self.term.source_interface
+
+ destination_interface = ''
+ if self.term.destination_interface:
+ destination_interface = self.term.destination_interface
+
+ log_hits = False
+ if self.term.logging:
+ # Iptables sends logs to hosts configured syslog
+ log_hits = True
+
+ # options
+ tcp_flags = []
+ tcp_track_options = []
+ for next_opt in [str(x) for x in self.term.option]:
+ #
+ # Sanity checking and high-ports are added as appropriate in
+ # pre-processing that is done in __str__ within class Iptables.
+ # Option established will add destination port high-ports if protocol
+ # contains only tcp, udp or both. This is done earlier in class Iptables.
+ #
+ if ((next_opt.find('established') == 0 or
+ next_opt.find('tcp-established') == 0)
+ and 'ESTABLISHED' not in [x.strip() for x in self.options]):
+ if next_opt.find('tcp-established') == 0 and protocol != ['tcp']:
+ raise TcpEstablishedError('%s %s %s' % (
+ '\noption tcp-established can only be applied for proto tcp.',
+ '\nError in term:', self.term.name))
+
+ if self.trackstate:
+ # Use nf_conntrack to track state -- works with any proto
+ self.options.append('-m state --state ESTABLISHED,RELATED')
+ elif protocol == ['tcp']:
+ # Simple established-only rule for TCP: Must have ACK field
+ # (SYN/ACK or subsequent ACK), or RST and no other flags.
+ tcp_track_options = [(['ACK'], ['ACK']),
+ (['SYN', 'FIN', 'ACK', 'RST'], ['RST'])]
+
+ # Iterate through flags table, and create list of tcp-flags to append
+ for next_flag in self._TCP_FLAGS_TABLE:
+ if next_opt.find(next_flag) == 0:
+ tcp_flags.append(self._TCP_FLAGS_TABLE.get(next_flag))
+ if next_opt in self._KNOWN_OPTIONS_MATCHERS:
+ self.options.append(self._KNOWN_OPTIONS_MATCHERS[next_opt])
+ if self.term.packet_length:
+ # Policy format is "#-#", but iptables format is "#:#"
+ self.options.append('-m length --length %s' %
+ self.term.packet_length.replace('-', ':'))
+ if self.term.fragment_offset:
+ self.options.append('-m u32 --u32 4&0x1FFF=%s' %
+ self.term.fragment_offset.replace('-', ':'))
+
+ for saddr in exclude_saddr:
+ ret_str.extend(self._FormatPart(
+ '', saddr, '', '', '', '', '', '', '', '', '', '',
+ self._ACTION_TABLE.get('next')))
+ for daddr in exclude_daddr:
+ ret_str.extend(self._FormatPart(
+ '', '', '', daddr, '', '', '', '', '', '', '', '',
+ self._ACTION_TABLE.get('next')))
+
+ for saddr in term_saddr:
+ for daddr in term_daddr:
+ for icmp in icmp_types:
+ for proto in protocol:
+ for tcp_matcher in tcp_track_options or (([], []),):
+ ret_str.extend(self._FormatPart(
+ str(proto),
+ saddr,
+ source_port,
+ daddr,
+ destination_port,
+ self.options,
+ tcp_flags,
+ icmp,
+ tcp_matcher,
+ source_interface,
+ destination_interface,
+ log_hits,
+ self._ACTION_TABLE.get(str(self.term.action[0]))
+ ))
+
+ if self._POSTJUMP_FORMAT:
+ ret_str.append(self._POSTJUMP_FORMAT.substitute(filter=self.filter,
+ term=self.term_name))
+
+ return '\n'.join(str(v) for v in ret_str if v is not '')
+
+ def _CalculateAddresses(self, term_saddr, exclude_saddr,
+ term_daddr, exclude_daddr):
+ """Calculate source and destination address list for a term.
+
+ Args:
+ term_saddr: source address list of the term
+ exclude_saddr: source address exclude list of the term
+ term_daddr: destination address list of the term
+ exclude_daddr: destination address exclude list of the term
+
+ Returns:
+ tuple containing source address list, source exclude address list,
+ destination address list, destination exclude address list in
+ that order
+
+ """
+ # source address
+ term_saddr_excluded = []
+ if not term_saddr:
+ term_saddr = [self._all_ips]
+ if exclude_saddr:
+ term_saddr_excluded.extend(nacaddr.ExcludeAddrs(term_saddr,
+ exclude_saddr))
+
+ # destination address
+ term_daddr_excluded = []
+ if not term_daddr:
+ term_daddr = [self._all_ips]
+ if exclude_daddr:
+ term_daddr_excluded.extend(nacaddr.ExcludeAddrs(term_daddr,
+ exclude_daddr))
+
+ # Just to be safe, always have a result of at least 1 to avoid * by zero
+ # returning incorrect results (10src*10dst=100, but 10src*0dst=0, not 10)
+ bailout_count = len(exclude_saddr) + len(exclude_daddr) + (
+ (len(self.term.source_address) or 1) *
+ (len(self.term.destination_address) or 1))
+ exclude_count = ((len(term_saddr_excluded) or 1) *
+ (len(term_daddr_excluded) or 1))
+
+ # Use bailout jumps for excluded addresses if it results in fewer output
+ # lines than nacaddr.ExcludeAddrs() method.
+ if exclude_count < bailout_count:
+ exclude_saddr = []
+ exclude_daddr = []
+ if term_saddr_excluded:
+ term_saddr = term_saddr_excluded
+ if term_daddr_excluded:
+ term_daddr = term_daddr_excluded
+
+ # With many sources and destinations, iptables needs to generate the
+ # cartesian product of sources and destinations. If there are no
+ # exclude rules, this can instead be written as exclude [0/0 -
+ # srcs], exclude [0/0 - dsts].
+ v4_src_count = len([x for x in term_saddr if x.version == 4])
+ v4_dst_count = len([x for x in term_daddr if x.version == 4])
+ v6_src_count = len([x for x in term_saddr if x.version == 6])
+ v6_dst_count = len([x for x in term_daddr if x.version == 6])
+ num_pairs = v4_src_count * v4_dst_count + v6_src_count * v6_dst_count
+ if num_pairs > 100:
+ new_exclude_source = nacaddr.ExcludeAddrs([self._all_ips], term_saddr)
+ new_exclude_dest = nacaddr.ExcludeAddrs([self._all_ips], term_daddr)
+ # Invert the shortest list that does not already have exclude addresses
+ if len(new_exclude_source) < len(new_exclude_dest) and not exclude_saddr:
+ if len(new_exclude_source) + len(term_daddr) < num_pairs:
+ exclude_saddr = new_exclude_source
+ term_saddr = [self._all_ips]
+ elif not exclude_daddr:
+ if len(new_exclude_dest) + len(term_saddr) < num_pairs:
+ exclude_daddr = new_exclude_dest
+ term_daddr = [self._all_ips]
+ term_saddr = [x for x in term_saddr
+ if x.version == self.AF_MAP[self.af]]
+ exclude_saddr = [x for x in exclude_saddr
+ if x.version == self.AF_MAP[self.af]]
+ term_daddr = [x for x in term_daddr
+ if x.version == self.AF_MAP[self.af]]
+ exclude_daddr = [x for x in exclude_daddr
+ if x.version == self.AF_MAP[self.af]]
+ return (term_saddr, exclude_saddr, term_daddr, exclude_daddr)
+
+ def _FormatPart(self, protocol, saddr, sport, daddr, dport, options,
+ tcp_flags, icmp_type, track_flags, sint, dint, log_hits,
+ action):
+ """Compose one iteration of the term parts into a string.
+
+ Args:
+ protocol: The network protocol
+ saddr: Source IP address
+ sport: Source port numbers
+ daddr: Destination IP address
+ dport: Destination port numbers
+ options: Optional arguments to append to our rule
+ tcp_flags: Which tcp_flag arguments, if any, should be appended
+ icmp_type: What icmp protocol to allow, if any
+ track_flags: A tuple of ([check-flags], [set-flags]) arguments to tcp-flag
+ sint: Optional source interface
+ dint: Optional destination interface
+ log_hits: Boolean, to log matches or not
+ action: What should happen if this rule matches
+ Returns:
+ rval: A single iptables argument line
+ """
+ src, dst = self._GenerateAddressStatement(saddr, daddr)
+
+ filter_top = self._FILTER_TOP_FORMAT.substitute(filter=self.filter,
+ term=self.term_name)
+
+ source_int = ''
+ if sint:
+ source_int = '-i %s' % sint
+
+ destination_int = ''
+ if dint:
+ destination_int = '-o %s' % dint
+
+ log_jump = ''
+ if log_hits:
+ log_jump = '-j LOG --log-prefix %s ' % self.term.name
+
+ if not options:
+ options = []
+
+ proto = self._PROTO_TABLE.get(str(protocol))
+ # Don't drop protocol if we don't recognize it
+ if protocol and not proto:
+ proto = '-p %s' % str(protocol)
+
+ # set conntrack state to NEW, unless policy requested "nostate"
+ if self.trackstate:
+ already_stateful = False
+ # we will add new stateful arguments only if none already exist, such
+ # as from "option:: established"
+ for option in options:
+ if 'state' in option:
+ already_stateful = True
+ if not already_stateful:
+ if 'ACCEPT' in action:
+ # We have to permit established/related since a policy may not
+ # have an existing blank permit for established/related, which
+ # may be more efficient, but slightly less secure.
+ options.append('-m state --state NEW,ESTABLISHED,RELATED')
+
+ if tcp_flags or (track_flags and track_flags[0]):
+ check_fields = ','.join(sorted(set(tcp_flags + track_flags[0])))
+ set_fields = ','.join(sorted(set(tcp_flags + track_flags[1])))
+ flags = '--tcp-flags %s %s' % (check_fields, set_fields)
+ else:
+ flags = ''
+
+ icmp_type = str(icmp_type)
+ if not icmp_type:
+ icmp = ''
+ elif str(protocol) == 'icmpv6':
+ icmp = '--icmpv6-type %s' % icmp_type
+ else:
+ icmp = '--icmp-type %s' % icmp_type
+
+ # format tcp and udp ports
+ sports = dports = ['']
+ if sport:
+ sports = self._GeneratePortStatement(sport, source=True)
+ if dport:
+ dports = self._GeneratePortStatement(dport, dest=True)
+
+ ret_lines = []
+ for sport in sports:
+ for dport in dports:
+ rval = [filter_top]
+ if re.search('multiport', sport) and not re.search('multiport', dport):
+ # Due to bug in iptables, use of multiport module before a single
+ # port specification will result in multiport trying to consume it.
+ # this is a little hack to ensure single ports are listed before
+ # any multiport specification.
+ dport, sport = sport, dport
+ for value in (proto, flags, sport, dport, icmp, src, dst,
+ ' '.join(options), source_int, destination_int):
+ if value:
+ rval.append(str(value))
+ if log_jump:
+ # -j LOG
+ ret_lines.append(' '.join(rval+[log_jump]))
+ # -j ACTION
+ ret_lines.append(' '.join(rval+[action]))
+ return ret_lines
+
+ def _GenerateAddressStatement(self, saddr, daddr):
+ """Return the address section of an individual iptables rule.
+
+ Args:
+ saddr: source address of the rule
+ daddr: destination address of the rule
+
+ Returns:
+ tuple containing source and destination address statement, in
+ that order
+
+ """
+ src = ''
+ dst = ''
+ if not saddr or saddr == self._all_ips:
+ src = ''
+ else:
+ src = '-s %s/%d' % (saddr.ip, saddr.prefixlen)
+ if not daddr or daddr == self._all_ips:
+ dst = ''
+ else:
+ dst = '-d %s/%d' % (daddr.ip, daddr.prefixlen)
+ return (src, dst)
+
+ def _GeneratePortStatement(self, ports, source=False, dest=False):
+ """Return the 'port' section of an individual iptables rule.
+
+ Args:
+ ports: list of ports or port ranges (pairs)
+ source: (bool) generate a source port rule
+ dest: (bool) generate a dest port rule
+
+ Returns:
+ list holding the 'port' sections of an iptables rule.
+
+ Raises:
+ BadPortsError: if too many ports are passed in, or if both 'source'
+ and 'dest' are true.
+ NotImplementedError: if both 'source' and 'dest' are true.
+ """
+ if not ports:
+ return ''
+
+ direction = '' # default: no direction / '--port'. As yet, unused.
+ if source and dest:
+ raise BadPortsError('_GeneratePortStatement called ambiguously.')
+ elif source:
+ direction = 's' # source port / '--sport'
+ elif dest:
+ direction = 'd' # dest port / '--dport'
+ else:
+ raise NotImplementedError('--port support not yet implemented.')
+
+ # Normalize ports and get accurate port count.
+ # iptables multiport module limits to 15, but we use 14 to ensure a range
+ # doesn't tip us over the limit
+ max_ports = 14
+ norm_ports = []
+ portstrings = []
+ count = 0
+ for port in ports:
+ if port[0] == port[1]:
+ norm_ports.append(str(port[0]))
+ count += 1
+ else:
+ norm_ports.append('%d:%d' % (port[0], port[1]))
+ count += 2
+ if count >= max_ports:
+ count = 0
+ portstrings.append('-m multiport --%sports %s' % (direction,
+ ','.join(norm_ports)))
+ norm_ports = []
+ if len(norm_ports) == 1:
+ portstrings.append('--%sport %s' % (direction, norm_ports[0]))
+ else:
+ portstrings.append('-m multiport --%sports %s' % (direction,
+ ','.join(norm_ports)))
+ return portstrings
+
+
+class Iptables(aclgenerator.ACLGenerator):
+ """Generates filters and terms from provided policy object."""
+
+ _PLATFORM = 'iptables'
+ _DEFAULT_PROTOCOL = 'all'
+ _SUFFIX = ''
+ _RENDER_PREFIX = None
+ _RENDER_SUFFIX = None
+ _DEFAULTACTION_FORMAT = '-P %s %s'
+ _DEFAULT_ACTION = 'DROP'
+ _TERM = Term
+ _TERM_MAX_LENGTH = 24
+ _OPTIONAL_SUPPORTED_KEYWORDS = set(['counter',
+ 'destination_interface',
+ 'destination_prefix', # skips these terms
+ 'expiration',
+ 'fragment_offset',
+ 'logging',
+ 'owner',
+ 'packet_length',
+ 'policer', # safely ignored
+ 'qos',
+ 'routing_instance', # safe to skip
+ 'source_interface',
+ 'source_prefix', # skips these terms
+ ])
+
+ def _TranslatePolicy(self, pol, exp_info):
+ """Translate a policy from objects into strings."""
+ self.iptables_policies = []
+ current_date = datetime.date.today()
+ exp_info_date = current_date + datetime.timedelta(weeks=exp_info)
+
+ default_action = None
+ good_default_actions = ['ACCEPT', 'DROP']
+ good_filters = ['INPUT', 'OUTPUT', 'FORWARD']
+ good_afs = ['inet', 'inet6']
+ good_options = ['nostate', 'abbreviateterms', 'truncateterms']
+ all_protocols_stateful = True
+
+ for header, terms in pol.filters:
+ filter_type = None
+ if self._PLATFORM not in header.platforms:
+ continue
+
+ filter_options = header.FilterOptions(self._PLATFORM)[1:]
+ filter_name = header.FilterName(self._PLATFORM)
+
+ if filter_name not in good_filters:
+ logging.warn('Filter is generating a non-standard chain that will not '
+ 'apply to traffic unless linked from INPUT, OUTPUT or '
+ 'FORWARD filters. New chain name is: %s', filter_name)
+
+ # ensure all options after the filter name are expected
+ for opt in filter_options:
+ if opt not in good_default_actions + good_afs + good_options:
+ raise UnsupportedTargetOption('%s %s %s %s' % (
+ '\nUnsupported option found in', self._PLATFORM,
+ 'target definition:', opt))
+
+ # disable stateful?
+ if 'nostate' in filter_options:
+ all_protocols_stateful = False
+
+ # Check for matching af
+ for address_family in good_afs:
+ if address_family in filter_options:
+ # should not specify more than one AF in options
+ if filter_type is not None:
+ raise UnsupportedFilterError('%s %s %s %s' % (
+ '\nMay only specify one of', good_afs, 'in filter options:',
+ filter_options))
+ filter_type = address_family
+ if filter_type is None:
+ filter_type = 'inet'
+
+ if self._PLATFORM == 'iptables' and filter_name == 'FORWARD':
+ default_action = 'DROP'
+
+ # does this policy override the default filter actions?
+ for next_target in header.target:
+ if next_target.platform == self._PLATFORM:
+ if len(next_target.options) > 1:
+ for arg in next_target.options:
+ if arg in good_default_actions:
+ default_action = arg
+ if default_action and default_action not in good_default_actions:
+ raise UnsupportedDefaultAction('%s %s %s %s %s' % (
+ '\nOnly', ', '.join(good_default_actions),
+ 'default filter action allowed;', default_action, 'used.'))
+
+ # add the terms
+ new_terms = []
+ term_names = set()
+ for term in terms:
+ term.name = self.FixTermLength(term.name,
+ 'abbreviateterms' in filter_options,
+ 'truncateterms' in filter_options)
+ if term.name in term_names:
+ raise aclgenerator.DuplicateTermError(
+ 'You have a duplicate term: %s' % term.name)
+ term_names.add(term.name)
+
+ term = self.FixHighPorts(term, af=filter_type,
+ all_protocols_stateful=all_protocols_stateful)
+ if not term:
+ continue
+
+ if term.expiration:
+ if term.expiration <= exp_info_date:
+ logging.info('INFO: Term %s in policy %s expires '
+ 'in less than two weeks.', term.name, filter_name)
+ if term.expiration <= current_date:
+ logging.warn('WARNING: Term %s in policy %s is expired and '
+ 'will not be rendered.', term.name, filter_name)
+ continue
+
+ new_terms.append(self._TERM(term, filter_name, all_protocols_stateful,
+ default_action, filter_type))
+
+ self.iptables_policies.append((header, filter_name, filter_type,
+ default_action, new_terms))
+
+ def __str__(self):
+ target = []
+ pretty_platform = '%s%s' % (self._PLATFORM[0].upper(), self._PLATFORM[1:])
+
+ if self._RENDER_PREFIX:
+ target.append(self._RENDER_PREFIX)
+
+ for (header, filter_name, filter_type, default_action, terms
+ ) in self.iptables_policies:
+ # Add comments for this filter
+ target.append('# %s %s Policy' % (pretty_platform,
+ header.FilterName(self._PLATFORM)))
+
+ # reformat long text comments, if needed
+ comments = aclgenerator.WrapWords(header.comment, 70)
+ if comments and comments[0]:
+ for line in comments:
+ target.append('# %s' % line)
+ target.append('#')
+ # add the p4 tags
+ target.extend(aclgenerator.AddRepositoryTags('# '))
+ target.append('# ' + filter_type)
+
+ # always specify the default filter states for speedway,
+ # if default action policy not specified for iptables, do nothing.
+ if self._PLATFORM == 'speedway':
+ if not default_action:
+ target.append(self._DEFAULTACTION_FORMAT % (filter_name,
+ self._DEFAULT_ACTION))
+ if default_action:
+ target.append(self._DEFAULTACTION_FORMAT % (filter_name,
+ default_action))
+ # add the terms
+ for term in terms:
+ term_str = str(term)
+ if term_str:
+ target.append(term_str)
+
+ if self._RENDER_SUFFIX:
+ target.append(self._RENDER_SUFFIX)
+
+ target.append('')
+ return '\n'.join(target)
+
+
+class Error(Exception):
+ """Base error class."""
+
+
+class BadPortsError(Error):
+ """Too many ports for a single iptables statement."""
+
+
+class UnsupportedFilterError(Error):
+ """Raised when we see an inappropriate filter."""
+
+
+class NoIptablesPolicyError(Error):
+ """Raised when a policy is received that doesn't support iptables."""
+
+
+class TcpEstablishedError(Error):
+ """Raised when a term has tcp-established option but not proto tcp only."""
+
+
+class EstablishedError(Error):
+ """Raised when a term has established option with inappropriate protocol."""
+
+
+class UnsupportedDefaultAction(Error):
+ """Raised when a filter has an impermissible default action specified."""
+
+
+class UnsupportedTargetOption(Error):
+ """Raised when a filter has an impermissible default action specified."""