Commit a0fa0a75 authored by Florent Fourcot's avatar Florent Fourcot Committed by Peter V. Saveliev

ipset: support more ipset types (#289)

We already can create ipsets of several types, but that is not enough
to really use them. Indeed, we must support all entries types in the add and
delete commands.

This patch begins the support of other types than IPv4/IPv6 (like iface
and net) and add the support of ipsets with several dimensions
(hash:net,iface for example).

It adds some complexity to parse entries before to add it, since we must
use the good attribute value. Since we are not aware of the ipset type
when we try to insert an entry, the function caller must pass the type
as string (default is "ip", it does not break previous implementation).
parent c99963ef
......@@ -132,17 +132,35 @@ class IPSet(NetlinkSocket):
msg_flags=NLM_F_REQUEST | NLM_F_ACK | excl_flag,
terminate=_nlmsg_error)
def _entry_to_data_attrs(self, entry, etype, family):
attrs = []
if family is not None:
if family == socket.AF_INET:
ip_version = 'IPSET_ATTR_IPADDR_IPV4'
elif family == socket.AF_INET6:
ip_version = 'IPSET_ATTR_IPADDR_IPV6'
else:
raise TypeError('unknown family')
for e, t in zip(entry.split(','), etype.split(',')):
if t in ('ip', 'net'):
if t == 'net':
if '/' in e:
e, cidr = e.split('/')
attrs += [['IPSET_ATTR_CIDR', int(cidr)]]
elif '-' in e:
e, to = e.split('-')
attrs += [['IPSET_ATTR_IP_TO',
{'attrs': [[ip_version, to]]}]]
attrs += [['IPSET_ATTR_IP_FROM', {'attrs': [[ip_version, e]]}]]
elif t == 'iface':
attrs += [['IPSET_ATTR_IFACE', e]]
return attrs
def _add_delete(self, name, entry, family, cmd, exclusive, comment=None,
timeout=None):
if family == socket.AF_INET:
entry_type = 'IPSET_ATTR_IPADDR_IPV4'
elif family == socket.AF_INET6:
entry_type = 'IPSET_ATTR_IPADDR_IPV6'
else:
raise TypeError('unknown family')
timeout=None, etype="ip"):
excl_flag = NLM_F_EXCL if exclusive else 0
data_attrs = [['IPSET_ATTR_IP', {'attrs': [[entry_type, entry]]}]]
data_attrs = self._entry_to_data_attrs(entry, etype, family)
if comment is not None:
data_attrs += [["IPSET_ATTR_COMMENT", comment],
["IPSET_ATTR_CADT_LINENO", 0]]
......@@ -158,18 +176,20 @@ class IPSet(NetlinkSocket):
terminate=_nlmsg_error)
def add(self, name, entry, family=socket.AF_INET, exclusive=True,
comment=None, timeout=None):
comment=None, timeout=None, etype="ip"):
'''
Add a member to the ipset
'''
return self._add_delete(name, entry, family, IPSET_CMD_ADD, exclusive,
comment=comment, timeout=timeout)
comment=comment, timeout=timeout, etype=etype)
def delete(self, name, entry, family=socket.AF_INET, exclusive=True):
def delete(self, name, entry, family=socket.AF_INET, exclusive=True,
etype="ip"):
'''
Delete a member from the ipset
'''
return self._add_delete(name, entry, family, IPSET_CMD_DEL, exclusive)
return self._add_delete(name, entry, family, IPSET_CMD_DEL, exclusive,
etype=etype)
def swap(self, set_a, set_b):
'''
......
......@@ -62,10 +62,10 @@ class ipset_msg(nfgen_msg):
(1, 'IPSET_ATTR_IP', 'ipset_ip'),
(1, 'IPSET_ATTR_IP_FROM', 'ipset_ip'),
(2, 'IPSET_ATTR_IP_TO', 'ipset_ip'),
(3, 'IPSET_ATTR_CIDR', 'hex'),
(4, 'IPSET_ATTR_PORT', 'hex'),
(4, 'IPSET_ATTR_PORT_FROM', 'hex'),
(5, 'IPSET_ATTR_PORT_TO', 'hex'),
(3, 'IPSET_ATTR_CIDR', 'be8', NLA_F_NET_BYTEORDER),
(4, 'IPSET_ATTR_PORT', 'be16', NLA_F_NET_BYTEORDER),
(4, 'IPSET_ATTR_PORT_FROM', 'be16', NLA_F_NET_BYTEORDER),
(5, 'IPSET_ATTR_PORT_TO', 'be16', NLA_F_NET_BYTEORDER),
(6, 'IPSET_ATTR_TIMEOUT', 'be32', NLA_F_NET_BYTEORDER),
(7, 'IPSET_ATTR_PROTO', 'recursive'),
(8, 'IPSET_ATTR_CADT_FLAGS', 'be32', NLA_F_NET_BYTEORDER),
......@@ -73,12 +73,14 @@ class ipset_msg(nfgen_msg):
(10, 'IPSET_ATTR_MARK', 'hex'),
(11, 'IPSET_ATTR_MARKMASK', 'hex'),
(17, 'IPSET_ATTR_GC', 'hex'),
(17, 'IPSET_ATTR_ETHER', 'l2addr'),
(18, 'IPSET_ATTR_HASHSIZE', 'be32', NLA_F_NET_BYTEORDER),
(19, 'IPSET_ATTR_MAXELEM', 'be32', NLA_F_NET_BYTEORDER),
(20, 'IPSET_ATTR_NETMASK', 'hex'),
(21, 'IPSET_ATTR_PROBES', 'hex'),
(22, 'IPSET_ATTR_RESIZE', 'hex'),
(23, 'IPSET_ATTR_SIZE', 'hex'),
(23, 'IPSET_ATTR_IFACE', 'asciiz'),
(24, 'IPSET_ATTR_BYTES', 'be64'),
(25, 'IPSET_ATTR_PACKETS', 'be64'),
(26, 'IPSET_ATTR_COMMENT', 'asciiz'),
......@@ -92,23 +94,23 @@ class ipset_msg(nfgen_msg):
(1, 'IPSET_ATTR_IP', 'ipset_ip'),
(1, 'IPSET_ATTR_IP_FROM', 'ipset_ip'),
(2, 'IPSET_ATTR_IP_TO', 'ipset_ip'),
(3, 'IPSET_ATTR_CIDR', 'hex'),
(4, 'IPSET_ATTR_PORT', 'hex'),
(4, 'IPSET_ATTR_PORT_FROM', 'hex'),
(5, 'IPSET_ATTR_PORT_TO', 'hex'),
(3, 'IPSET_ATTR_CIDR', 'be8', NLA_F_NET_BYTEORDER),
(4, 'IPSET_ATTR_PORT', 'be16', NLA_F_NET_BYTEORDER),
(4, 'IPSET_ATTR_PORT_FROM', 'be16', NLA_F_NET_BYTEORDER),
(5, 'IPSET_ATTR_PORT_TO', 'be16', NLA_F_NET_BYTEORDER),
(6, 'IPSET_ATTR_TIMEOUT', 'be32', NLA_F_NET_BYTEORDER),
(7, 'IPSET_ATTR_PROTO', 'recursive'),
(8, 'IPSET_ATTR_CADT_FLAGS', 'be32', NLA_F_NET_BYTEORDER),
(9, 'IPSET_ATTR_CADT_LINENO', 'be32'),
(10, 'IPSET_ATTR_MARK', 'hex'),
(11, 'IPSET_ATTR_MARKMASK', 'hex'),
(17, 'IPSET_ATTR_ETHER', 'hex'),
(17, 'IPSET_ATTR_ETHER', 'l2addr'),
(18, 'PSET_ATTR_NAME', 'hex'),
(19, 'IPSET_ATTR_NAMEREF', 'be32'),
(20, 'IPSET_ATTR_IP2', 'be32'),
(21, 'IPSET_ATTR_CIDR2', 'hex'),
(22, 'IPSET_ATTR_IP2_TO', 'hex'),
(23, 'IPSET_ATTR_IFACE', 'hex'),
(23, 'IPSET_ATTR_IFACE', 'asciiz'),
(24, 'IPSET_ATTR_BYTES', 'be64'),
(25, 'IPSET_ATTR_PACKETS', 'be64'),
(26, 'IPSET_ATTR_COMMENT', 'asciiz'),
......
......@@ -14,20 +14,50 @@ class TestIPSet(object):
def teardown(self):
self.ip.close()
@staticmethod
def parse_ip(entry):
ip_from = entry.get_attr('IPSET_ATTR_IP_FROM')
return ip_from.get_attr('IPSET_ATTR_IPADDR_IPV4')
def parse_net(self, entry):
net = self.parse_ip(entry)
cidr = entry.get_attr("IPSET_ATTR_CIDR")
if cidr is not None:
net += '/{0}'.format(cidr)
return net
@staticmethod
def ipset_type_to_entry_type(ipset_type):
return ipset_type.split(':', 1)[1].split(',')
def list_ipset(self, name):
try:
res = {}
msg_list = self.ip.list(name)
adt = 'IPSET_ATTR_ADT'
proto = 'IPSET_ATTR_PROTO'
ipaddr = 'IPSET_ATTR_IPADDR_IPV4'
stype = 'IPSET_ATTR_TYPENAME'
for msg in msg_list:
for x in msg.get_attr(adt).get_attrs(proto):
ip = x.get_attr('IPSET_ATTR_IP_FROM').get_attr(ipaddr)
res[ip] = (x.get_attr("IPSET_ATTR_PACKETS"),
x.get_attr("IPSET_ATTR_BYTES"),
x.get_attr("IPSET_ATTR_COMMENT"),
x.get_attr("IPSET_ATTR_TIMEOUT"))
entry = ''
msg_stypes = msg.get_attr(stype)
if msg_stypes is None:
msg_stypes = 'hash:ip'
for st in self.ipset_type_to_entry_type(msg_stypes):
if st == "ip":
entry = self.parse_ip(x)
elif st == "net":
entry = self.parse_net(x)
elif st == 'iface':
entry += x.get_attr('IPSET_ATTR_IFACE')
entry += ","
entry = entry.strip(",")
res[entry] = (x.get_attr("IPSET_ATTR_PACKETS"),
x.get_attr("IPSET_ATTR_BYTES"),
x.get_attr("IPSET_ATTR_COMMENT"),
x.get_attr("IPSET_ATTR_TIMEOUT"))
return res
except:
return {}
......@@ -219,7 +249,6 @@ class TestIPSet(object):
self.ip.create(name, hashsize=64, maxelem=maxelem, forceadd=True)
fill_to_max_entries(name)
self.ip.add(name, ipaddr)
assert ipaddr in self.list_ipset(name)
self.ip.destroy(name)
......@@ -263,3 +292,32 @@ class TestIPSet(object):
sleep(3)
assert ip not in self.list_ipset(name)
self.ip.destroy(name)
def test_net_and_iface_stypes(self):
require_user('root')
name = str(uuid4())[:16]
test_values = (('hash:net', ('192.168.1.0/31', '192.168.12.0/24')),
('hash:net,iface', ('192.168.1.0/24,eth0',
'192.168.2.0/24,wlan0')))
for stype, test_values in test_values:
self.ip.create(name, stype=stype)
etype = stype.split(':', 1)[1]
assert self.get_ipset(name)
for entry in test_values:
self.ip.add(name, entry, etype=etype)
assert entry in self.list_ipset(name)
self.ip.delete(name, entry, etype=etype)
assert entry not in self.list_ipset(name)
self.ip.destroy(name)
assert not self.get_ipset(name)
def test_net_with_dash(self):
require_user('root')
name = str(uuid4())[:16]
stype = "hash:net"
self.ip.create(name, stype=stype)
# The kernel will split this kind of strings to subnets
self.ip.add(name, "192.168.1.0-192.168.1.33", etype="net")
assert "192.168.1.0/27" in self.list_ipset(name)
assert "192.168.1.32/31" in self.list_ipset(name)
self.ip.destroy(name)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment