summaryrefslogtreecommitdiff
path: root/lib/ipset.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ipset.py')
-rw-r--r--lib/ipset.py200
1 files changed, 200 insertions, 0 deletions
diff --git a/lib/ipset.py b/lib/ipset.py
new file mode 100644
index 0000000..2ff4fbb
--- /dev/null
+++ b/lib/ipset.py
@@ -0,0 +1,200 @@
+#!/usr/bin/python
+#
+# Copyright 2013 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.
+#
+
+"""Ipset iptables generator. This is a subclass of Iptables generator.
+
+ipset is a system inside the Linux kernel, which can very efficiently store
+and match IPv4 and IPv6 addresses. This can be used to dramatically increase
+performace of iptables firewall.
+
+"""
+
+__author__ = 'vklimovs@google.com (Vjaceslavs Klimovs)'
+
+from string import Template
+
+import iptables
+import nacaddr
+
+
+class Error(Exception):
+ pass
+
+
+class Term(iptables.Term):
+ """Single Ipset term representation."""
+
+ _PLATFORM = 'ipset'
+ _SET_MAX_LENGTH = 31
+ _POSTJUMP_FORMAT = None
+ _PREJUMP_FORMAT = None
+ _TERM_FORMAT = None
+ _COMMENT_FORMAT = Template('-A $filter -m comment --comment "$comment"')
+ _FILTER_TOP_FORMAT = Template('-A $filter')
+
+ def __init__(self, *args, **kwargs):
+ super(Term, self).__init__(*args, **kwargs)
+ # This stores tuples of set name and set contents, keyed by direction.
+ # For example:
+ # { 'src': ('term_name', [ipaddr object, ipaddr object]),
+ # 'dst': ('term_name', [ipaddr object, ipaddr object]) }
+ self.addr_sets = dict()
+
+ def _CalculateAddresses(self, src_addr_list, src_ex_addr_list,
+ dst_addr_list, dst_ex_addr_list):
+ """Calculate source and destination address list for a term.
+
+ Since ipset is very efficient at matching large number of
+ addresses, we never return eny exclude addresses. Instead
+ least positive match is calculated for both source and destination
+ addresses.
+
+ For source and destination address list, three cases are possible.
+ First case is when there is no addresses. In that case we return
+ _all_ips.
+ Second case is when there is strictly one address. In that case,
+ we optimize by not generating a set, and it's then the only
+ element of returned set.
+ Third case case is when there is more than one address in a set.
+ In that case we generate a set and also return _all_ips. Note the
+ difference to the first case where no set is actually generated.
+
+ Args:
+ src_addr_list: source address list of the term.
+ src_ex_addr_list: source address exclude list of the term.
+ dst_addr_list: destination address list of the term.
+ dst_ex_addr_list: 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.
+
+ """
+ if not src_addr_list:
+ src_addr_list = [self._all_ips]
+ src_addr_list = [src_addr for src_addr in src_addr_list if
+ src_addr.version == self.AF_MAP[self.af]]
+ if src_ex_addr_list:
+ src_ex_addr_list = [src_ex_addr for src_ex_addr in src_ex_addr_list if
+ src_ex_addr.version == self.AF_MAP[self.af]]
+ src_addr_list = nacaddr.ExcludeAddrs(src_addr_list, src_ex_addr_list)
+ if len(src_addr_list) > 1:
+ set_name = self._GenerateSetName(self.term.name, 'src')
+ self.addr_sets['src'] = (set_name, src_addr_list)
+ src_addr_list = [self._all_ips]
+
+ if not dst_addr_list:
+ dst_addr_list = [self._all_ips]
+ dst_addr_list = [dst_addr for dst_addr in dst_addr_list if
+ dst_addr.version == self.AF_MAP[self.af]]
+ if dst_ex_addr_list:
+ dst_ex_addr_list = [dst_ex_addr for dst_ex_addr in dst_ex_addr_list if
+ dst_ex_addr.version == self.AF_MAP[self.af]]
+ dst_addr_list = nacaddr.ExcludeAddrs(dst_addr_list, dst_ex_addr_list)
+ if len(dst_addr_list) > 1:
+ set_name = self._GenerateSetName(self.term.name, 'dst')
+ self.addr_sets['dst'] = (set_name, dst_addr_list)
+ dst_addr_list = [self._all_ips]
+ return (src_addr_list, [], dst_addr_list, [])
+
+ def _GenerateAddressStatement(self, src_addr, dst_addr):
+ """Return the address section of an individual iptables rule.
+
+ See _CalculateAddresses documentation. Three cases are possible here,
+ and they map directly to cases in _CalculateAddresses.
+ First, there can be no addresses for a direction (value is _all_ips then)
+ In that case we return empty string.
+ Second there can be stricly one address. In that case we return single
+ address match (-s or -d).
+ Third case, is when the value is _all_ips but also the set for particular
+ direction is present. That's when we return a set match.
+
+ Args:
+ src_addr: source address of the rule.
+ dst_addr: destination address of the rule.
+
+ Returns:
+ tuple containing source and destination address statement, in
+ that order.
+
+ """
+ src_addr_stmt = ''
+ dst_addr_stmt = ''
+ if src_addr and dst_addr:
+ if src_addr == self._all_ips:
+ if 'src' in self.addr_sets:
+ src_addr_stmt = ('-m set --set %s src' % self.addr_sets['src'][0])
+ else:
+ src_addr_stmt = '-s %s/%d' % (src_addr.ip, src_addr.prefixlen)
+ if dst_addr == self._all_ips:
+ if 'dst' in self.addr_sets:
+ dst_addr_stmt = ('-m set --set %s dst' % self.addr_sets['dst'][0])
+ else:
+ dst_addr_stmt = '-d %s/%d' % (dst_addr.ip, dst_addr.prefixlen)
+ return (src_addr_stmt, dst_addr_stmt)
+
+ def _GenerateSetName(self, term_name, suffix):
+ if self.af == 'inet6':
+ suffix += '-v6'
+ if len(term_name) + len(suffix) + 1 > self._SET_MAX_LENGTH:
+ term_name = term_name[:self._SET_MAX_LENGTH -
+ (len(term_name) + len(suffix) + 1)]
+ return term_name + '-' + suffix
+
+
+class Ipset(iptables.Iptables):
+ """Ipset generator."""
+ _PLATFORM = 'ipset'
+ _SET_TYPE = 'hash:net'
+ _SUFFIX = '.ips'
+ _TERM = Term
+
+ def __str__(self):
+ # Actual rendering happens in __str__, so it has to be called
+ # before we do set specific part.
+ iptables_output = iptables.Iptables.__str__(self)
+ output = []
+ for (_, _, _, _, terms) in self.iptables_policies:
+ for term in terms:
+ output.extend(self._GenerateSetConfig(term))
+ output.append(iptables_output)
+ return '\n'.join(output)
+
+ def _GenerateSetConfig(self, term):
+ """Generate set configuration for supplied term.
+
+ Args:
+ term: input term.
+
+ Returns:
+ string that is configuration of supplied term.
+
+ """
+ output = []
+ for direction in sorted(term.addr_sets, reverse=True):
+ set_hashsize = 2 ** len(term.addr_sets[direction][1]).bit_length()
+ set_maxelem = 2 ** len(term.addr_sets[direction][1]).bit_length()
+ output.append('create %s %s family %s hashsize %i maxelem %i' %
+ (term.addr_sets[direction][0],
+ self._SET_TYPE,
+ term.af,
+ set_hashsize,
+ set_maxelem))
+ for address in term.addr_sets[direction][1]:
+ output.append('add %s %s' % (term.addr_sets[direction][0], address))
+ return output