BIND 10 trac3188, updated. 6150a08f2dcb81d8b58c2815da03cd15c72863ca [3188] Finish socket cache and b10-init token handling code for filtering sockets
BIND 10 source code commits
bind10-changes at lists.isc.org
Wed Feb 26 13:29:40 UTC 2014
The branch, trac3188 has been updated
via 6150a08f2dcb81d8b58c2815da03cd15c72863ca (commit)
via 8fd270124e241cc4c4411c7844d6cad9fabc3e64 (commit)
from e13edb9dcb01b4c161d270f87facc468258cca02 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 6150a08f2dcb81d8b58c2815da03cd15c72863ca
Author: Mukund Sivaraman <muks at isc.org>
Date: Mon Feb 24 16:47:01 2014 +0530
[3188] Finish socket cache and b10-init token handling code for filtering sockets
commit 8fd270124e241cc4c4411c7844d6cad9fabc3e64
Author: Mukund Sivaraman <muks at isc.org>
Date: Mon Feb 24 16:46:54 2014 +0530
[3188] Add FilterSocket class for use in the cache
-----------------------------------------------------------------------
Summary of changes:
src/bin/bind10/init.py.in | 31 ++++
src/lib/python/isc/bind10/socket_cache.py | 180 ++++++++++++++++----
.../python/isc/bind10/tests/socket_cache_test.py | 121 ++++++++-----
src/lib/python/isc/net/parse.py | 12 ++
4 files changed, 273 insertions(+), 71 deletions(-)
-----------------------------------------------------------------------
diff --git a/src/bin/bind10/init.py.in b/src/bin/bind10/init.py.in
index 67491b3..153235a 100755
--- a/src/bin/bind10/init.py.in
+++ b/src/bin/bind10/init.py.in
@@ -374,6 +374,8 @@ class Init:
create_answer(0, self.get_processes())
elif command == "get_socket":
answer = self._get_socket(args)
+ elif command == "get_filter_socket":
+ answer = self._get_filter_socket(args)
elif command == "drop_socket":
if "token" not in args:
answer = isc.config.ccsession. \
@@ -972,6 +974,35 @@ class Init:
except Exception as e:
return isc.config.ccsession.create_answer(1, str(e))
+ def _get_filter_socket(self, args):
+ """
+ Implementation of the get_filter_socket CC command. It asks the cache
+ to provide the token and sends the information back.
+ """
+ try:
+ try:
+ port = isc.net.parse.port_parse(args['port'])
+ iface = isc.net.parse.interface_parse(args['interface'])
+ except KeyError as ke:
+ return \
+ isc.config.ccsession.create_answer(1,
+ "Missing parameter " +
+ str(ke))
+
+ # FIXME: This call contains blocking IPC. It is expected to be
+ # short, but if it turns out to be problem, we'll need to do
+ # something about it.
+ token = self._socket_cache.get_filter_token(port, iface)
+ return isc.config.ccsession.create_answer(0, {
+ 'token': token,
+ 'path': self._socket_path
+ })
+ except isc.bind10.socket_cache.SocketError as e:
+ return isc.config.ccsession.create_answer(CREATOR_SOCKET_ERROR,
+ str(e))
+ except Exception as e:
+ return isc.config.ccsession.create_answer(1, str(e))
+
def socket_request_handler(self, token, unix_socket):
"""
This function handles a token that comes over a unix_domain socket.
diff --git a/src/lib/python/isc/bind10/socket_cache.py b/src/lib/python/isc/bind10/socket_cache.py
index 1c5199c..9133b70 100644
--- a/src/lib/python/isc/bind10/socket_cache.py
+++ b/src/lib/python/isc/bind10/socket_cache.py
@@ -42,27 +42,12 @@ class ShareError(Exception):
"""
pass
-class Socket:
+class SocketBase:
"""
- This represents one socket cached by the cache program. This should never
- be used directly by a user, it is used internally by the Cache. Therefore
- many member variables are used directly instead of by a accessor method.
-
- Be warned that this object implements the __del__ method. It closes the
- socket held inside in it. But this poses various problems with garbage
- collector. In short, do not make reference cycles with this and generally
- leave this class alone to live peacefully.
+ This is a common base class to Socket and FilterSocket.
"""
- def __init__(self, protocol, address, port, fileno):
- """
- Creates the socket.
-
- The protocol, address and port are preserved for the information.
- """
- self.protocol = protocol
- self.address = address
- self.port = port
- self.fileno = fileno
+ def __init__(self):
+ self.fileno = None
# Mapping from token -> application
self.active_tokens = {}
# The tokens which were not yet picked up
@@ -74,7 +59,8 @@ class Socket:
"""
Closes the file descriptor.
"""
- os.close(self.fileno)
+ if self.fileno is not None:
+ os.close(self.fileno)
def share_compatible(self, mode, name):
"""
@@ -101,6 +87,56 @@ class Socket:
# No problem found, so we consider it OK
return True
+class Socket(SocketBase):
+ """
+ This represents one socket cached by the cache program. This should never
+ be used directly by a user, it is used internally by the Cache. Therefore
+ many member variables are used directly instead of by a accessor method.
+
+ Be warned that this object's base class implements the __del__
+ method. It closes the socket held inside in it. But this poses
+ various problems with garbage collector. In short, do not make
+ reference cycles with this and generally leave this class alone to
+ live peacefully.
+ """
+ def __init__(self, protocol, address, port, fileno):
+ """
+ Creates the socket.
+
+ The protocol, address and port are preserved for the information.
+ """
+ super().__init__()
+
+ self.protocol = protocol
+ self.address = address
+ self.port = port
+ self.fileno = fileno
+
+class FilterSocket(SocketBase):
+ """
+ This represents one filtering socket cached by the cache
+ program. This should never be used directly by a user, it is used
+ internally by the Cache. Therefore many member variables are used
+ directly instead of by a accessor method.
+
+ Be warned that this object's base class implements the __del__
+ method. It closes the socket held inside in it. But this poses
+ various problems with garbage collector. In short, do not make
+ reference cycles with this and generally leave this class alone to
+ live peacefully.
+ """
+ def __init__(self, port, interface, fileno):
+ """
+ Creates the socket.
+
+ The port and interface are preserved for their information.
+ """
+ super().__init__()
+
+ self.port = port
+ self.interface = interface
+ self.fileno = fileno
+
class Cache:
"""
This is the cache for sockets from socket creator. The purpose of cache
@@ -142,10 +178,19 @@ class Cache:
# The sockets live here to be indexed by protocol, address and
# subsequently by port
self._sockets = {}
+ # The filter sockets live here to be indexed by interface and port
+ self._filter_sockets = {}
# These are just the tokens actually in use, so we don't generate
# dupes. If one is dropped, it can be potentially reclaimed.
self._live_tokens = set()
+ def __make_token(self):
+ # Grab yet unused token
+ token = 't' + str(random.randint(0, 2 ** 32-1))
+ while token in self._live_tokens:
+ token = 't' + str(random.randint(0, 2 ** 32-1))
+ return token
+
def get_token(self, protocol, address, port, share_mode, share_name):
"""
This requests a token representing a socket. The socket is either
@@ -205,10 +250,69 @@ class Cache:
if not socket.share_compatible(share_mode, share_name):
raise ShareError("Cached socket not compatible with mode " +
share_mode + " and name " + share_name)
- # Grab yet unused token
- token = 't' + str(random.randint(0, 2 ** 32-1))
- while token in self._live_tokens:
- token = 't' + str(random.randint(0, 2 ** 32-1))
+ token = self.__make_token()
+ self._waiting_tokens[token] = socket
+ self._live_tokens.add(token)
+ socket.shares[token] = (share_mode, share_name)
+ socket.waiting_tokens.add(token)
+ return token
+
+ def get_filter_token(self, port, iface, share_mode, share_name):
+ """
+ This requests a token representing a raw filtering socket. The
+ socket is either found in the cache already or requested from
+ the creator at this time (and cached for later time).
+
+ The parameters are:
+ - port: integer saying which port to filter for
+ - iface: integer saying which interface to filter on
+ - share_mode: either 'NO', 'SAMEAPP' or 'ANY', specifying how the
+ socket can be shared with others. See bin/bind10/creatorapi.txt
+ for details.
+ - share_name: the name of application, in case of 'SAMEAPP' share
+ mode. Only requests with the same name can share the socket.
+
+ If the call is successful, it returns a string token which can be
+ used to pick up the socket later. The socket is created with reference
+ count zero and if it isn't picked up soon enough (the time yet has to
+ be set), it will be removed and the token is invalid.
+
+ It can fail in various ways. Explicitly listed exceptions are:
+ - SocketError: this one is thrown if the socket creator couldn't provide
+ the socket and it is not yet cached (it belongs to other application,
+ for example).
+ - ShareError: the socket is already in the cache, but it can't be
+ shared due to share_mode and share_name combination (both the request
+ restrictions and of all copies of socket handed out are considered,
+ so it can be raised even if you call it with share_mode 'ANY').
+ - isc.bind10.sockcreator.CreatorError: fatal creator errors are
+ propagated. Thay should cause b10-init to exit if ever encountered.
+
+ Note that it isn't guaranteed the tokens would be unique and they
+ should be used as an opaque handle only.
+ """
+ try:
+ socket = self._filter_sockets[iface][port]
+ except KeyError:
+ # Something in the dicts is not there, so socket is to be
+ # created
+ try:
+ fileno = self._creator.get_filter_socket(port, iface)
+ except isc.bind10.sockcreator.CreatorError as ce:
+ if ce.fatal:
+ raise
+ else:
+ raise SocketError(str(ce), ce.errno)
+ socket = FilterSocket(port, iface, fileno)
+ # And cache it
+ if iface not in self._filter_sockets:
+ self._filter_sockets[iface] = {}
+ self._filter_sockets[iface][port] = socket
+ # Now we get the token, check it is compatible
+ if not socket.share_compatible(share_mode, share_name):
+ raise ShareError("Cached socket not compatible with mode " +
+ share_mode + " and name " + share_name)
+ token = self.__make_token()
self._waiting_tokens[token] = socket
self._live_tokens.add(token)
socket.shares[token] = (share_mode, share_name)
@@ -270,15 +374,25 @@ class Cache:
self._live_tokens.remove(token)
# The socket is not used by anything now, so remove it
if len(socket.active_tokens) == 0 and len(socket.waiting_tokens) == 0:
- addr = str(socket.address)
- port = socket.port
- proto = socket.protocol
- del self._sockets[proto][addr][port]
- # Clean up empty branches of the structure
- if len(self._sockets[proto][addr]) == 0:
- del self._sockets[proto][addr]
- if len(self._sockets[proto]) == 0:
- del self._sockets[proto]
+ if isinstance(socket, FilterSocket):
+ port = socket.port
+ iface = socket.interface
+ del self._filter_sockets[iface][port]
+ # Clean up empty branches of the structure
+ if len(self._filter_sockets[iface]) == 0:
+ del self._filter_sockets[iface]
+ elif isinstance(socket, Socket):
+ addr = str(socket.address)
+ port = socket.port
+ proto = socket.protocol
+ del self._sockets[proto][addr][port]
+ # Clean up empty branches of the structure
+ if len(self._sockets[proto][addr]) == 0:
+ del self._sockets[proto][addr]
+ if len(self._sockets[proto]) == 0:
+ del self._sockets[proto]
+ else:
+ raise
def drop_application(self, application):
"""
diff --git a/src/lib/python/isc/bind10/tests/socket_cache_test.py b/src/lib/python/isc/bind10/tests/socket_cache_test.py
index 6a53a27..1fc6873 100644
--- a/src/lib/python/isc/bind10/tests/socket_cache_test.py
+++ b/src/lib/python/isc/bind10/tests/socket_cache_test.py
@@ -41,6 +41,54 @@ class Test(unittest.TestCase):
"""
self._closes.append(fd)
+ def share_modes_test_helper(self, testsocket):
+ """
+ Test the share mode compatibility check function.
+ """
+ modes = ['NO', 'SAMEAPP', 'ANY']
+ # If there are no shares, it is compatible with everything.
+ for mode in modes:
+ self.assertTrue(testsocket.share_compatible(mode, 'anything'))
+
+ # There's an NO already, so it is incompatible with everything.
+ testsocket.shares = {'token': ('NO', 'anything')}
+ for mode in modes:
+ self.assertFalse(testsocket.share_compatible(mode, 'anything'))
+
+ # If there's SAMEAPP, it is compatible with ANY and SAMEAPP with the
+ # same name.
+ testsocket.shares = {'token': ('SAMEAPP', 'app')}
+ self.assertFalse(testsocket.share_compatible('NO', 'app'))
+ self.assertFalse(testsocket.share_compatible('SAMEAPP',
+ 'something'))
+ self.assertTrue(testsocket.share_compatible('SAMEAPP', 'app'))
+ self.assertTrue(testsocket.share_compatible('ANY', 'app'))
+ self.assertFalse(testsocket.share_compatible('ANY', 'something'))
+
+ # If there's ANY, then ANY and SAMEAPP with the same name is compatible
+ testsocket.shares = {'token': ('ANY', 'app')}
+ self.assertFalse(testsocket.share_compatible('NO', 'app'))
+ self.assertFalse(testsocket.share_compatible('SAMEAPP',
+ 'something'))
+ self.assertTrue(testsocket.share_compatible('SAMEAPP', 'app'))
+ self.assertTrue(testsocket.share_compatible('ANY', 'something'))
+
+ # In case there are multiple already inside
+ testsocket.shares = {
+ 'token': ('ANY', 'app'),
+ 'another': ('SAMEAPP', 'app')
+ }
+ self.assertFalse(testsocket.share_compatible('NO', 'app'))
+ self.assertFalse(testsocket.share_compatible('SAMEAPP',
+ 'something'))
+ self.assertTrue(testsocket.share_compatible('SAMEAPP', 'app'))
+ self.assertFalse(testsocket.share_compatible('ANY', 'something'))
+ self.assertTrue(testsocket.share_compatible('ANY', 'app'))
+
+ # Invalid inputs are rejected
+ self.assertRaises(ValueError, testsocket.share_compatible, 'bad',
+ 'bad')
+
class SocketTest(Test):
"""
Test for the Socket class.
@@ -81,49 +129,46 @@ class SocketTest(Test):
"""
Test the share mode compatibility check function.
"""
- modes = ['NO', 'SAMEAPP', 'ANY']
- # If there are no shares, it is compatible with everything.
- for mode in modes:
- self.assertTrue(self.__socket.share_compatible(mode, 'anything'))
+ self.share_modes_test_helper(self.__socket)
- # There's an NO already, so it is incompatible with everything.
- self.__socket.shares = {'token': ('NO', 'anything')}
- for mode in modes:
- self.assertFalse(self.__socket.share_compatible(mode, 'anything'))
+class FilterSocketTest(Test):
+ """
+ Test for the FilterSocket class.
+ """
+ def setUp(self):
+ """
+ Creates the socket to be tested.
- # If there's SAMEAPP, it is compatible with ANY and SAMEAPP with the
- # same name.
- self.__socket.shares = {'token': ('SAMEAPP', 'app')}
- self.assertFalse(self.__socket.share_compatible('NO', 'app'))
- self.assertFalse(self.__socket.share_compatible('SAMEAPP',
- 'something'))
- self.assertTrue(self.__socket.share_compatible('SAMEAPP', 'app'))
- self.assertTrue(self.__socket.share_compatible('ANY', 'app'))
- self.assertFalse(self.__socket.share_compatible('ANY', 'something'))
+ It also creates other useful test variables.
+ """
+ Test.setUp(self)
+ self.__socket = isc.bind10.socket_cache.FilterSocket(2047, 1, 43)
- # If there's ANY, then ANY and SAMEAPP with the same name is compatible
- self.__socket.shares = {'token': ('ANY', 'app')}
- self.assertFalse(self.__socket.share_compatible('NO', 'app'))
- self.assertFalse(self.__socket.share_compatible('SAMEAPP',
- 'something'))
- self.assertTrue(self.__socket.share_compatible('SAMEAPP', 'app'))
- self.assertTrue(self.__socket.share_compatible('ANY', 'something'))
+ def test_init(self):
+ """
+ Checks the internals of the cache just after the creation.
+ """
+ self.assertEqual(2047, self.__socket.port)
+ self.assertEqual(1, self.__socket.interface)
+ self.assertEqual(43, self.__socket.fileno)
+ self.assertEqual({}, self.__socket.active_tokens)
+ self.assertEqual({}, self.__socket.shares)
+ self.assertEqual(set(), self.__socket.waiting_tokens)
- # In case there are multiple already inside
- self.__socket.shares = {
- 'token': ('ANY', 'app'),
- 'another': ('SAMEAPP', 'app')
- }
- self.assertFalse(self.__socket.share_compatible('NO', 'app'))
- self.assertFalse(self.__socket.share_compatible('SAMEAPP',
- 'something'))
- self.assertTrue(self.__socket.share_compatible('SAMEAPP', 'app'))
- self.assertFalse(self.__socket.share_compatible('ANY', 'something'))
- self.assertTrue(self.__socket.share_compatible('ANY', 'app'))
+ def test_del(self):
+ """
+ Check it closes the socket when removed.
+ """
+ # This should make the refcount 0 and call the destructor
+ # right away
+ self.__socket = None
+ self.assertEqual([43], self._closes)
- # Invalid inputs are rejected
- self.assertRaises(ValueError, self.__socket.share_compatible, 'bad',
- 'bad')
+ def test_share_modes(self):
+ """
+ Test the share mode compatibility check function.
+ """
+ self.share_modes_test_helper(self.__socket)
class SocketCacheTest(Test):
"""
diff --git a/src/lib/python/isc/net/parse.py b/src/lib/python/isc/net/parse.py
index 30edadc..b5d1456 100644
--- a/src/lib/python/isc/net/parse.py
+++ b/src/lib/python/isc/net/parse.py
@@ -35,6 +35,18 @@ def port_parse(port):
" too large, allowed range is 0-65535")
return inted
+def interface_parse(iface):
+ """
+ Takes an interface as an int or string and checks if it is valid. It
+ returns the interface as int. If it is not a valid interface (the string
+ doesn't contain number or it is not in the valid range), it raises
+ ValueError.
+ """
+ inted = int(iface)
+ if inted < 0:
+ raise ValueError("Interface value " + str(inted) + " too small")
+ return inted
+
def addr_parse(addr):
"""
Checks and parses an IP address (either IPv4 or IPv6) and returns
More information about the bind10-changes
mailing list