Commit 517c31a5 authored by Michal 'vorner' Vaner's avatar Michal 'vorner' Vaner
Browse files

[trac800] The parser

parent 4c485d0b
......@@ -13,6 +13,9 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import socket
import struct
"""
Module that comunicates with the priviledget socket creator (b10-sockcreator).
"""
......@@ -55,14 +58,26 @@ class Parser:
have a read_fd() method to read the file descriptor. This slightly
unusual modification of socket object is used to easy up testing.
"""
pass # TODO Implement
self.__socket = creator_socket
def terminate(self):
"""
Asks the creator process to terminate and waits for it to close the
socket. Does not return anything.
"""
pass # TODO Implement
if self.__socket is None:
raise CreatorError('Terminated already', True)
try:
self.__socket.sendall(b'T')
# Wait for an EOF - it will return empty data
eof = self.__socket.recv(1)
if len(eof) != 0:
raise CreatorError('Protocol error - data after terminated',
True)
self.__socket = None
except socket.error as se:
self.__socket = None
raise CreatorError(str(se), True)
def get_socket(self, address, port, socktype):
"""
......@@ -75,4 +90,64 @@ class Parser:
should be fast, as it is on localhost) and returns the file descriptor
number. It raises a CreatorError exception if the creation fails.
"""
pass # TODO Implement
if self.__socket is None:
raise CreatorError('Socket requested on terminated creator', True)
# First, assemble the request from parts
data = b'S'
if socktype == 'UDP' or socktype == socket.SOCK_DGRAM:
data += b'U'
elif socktype == 'TCP' or socktype == socket.SOCK_STREAM:
data += b'T'
else:
raise ValueError('Unknown socket type: ' + str(socktype))
if address.family == socket.AF_INET:
data += b'4'
elif address.family == socket.AF_INET6:
data += b'6'
else:
raise ValueError('Unknown address family in address')
data += struct.pack('!H', port)
data += address.addr
try:
# Send the request
self.__socket.sendall(data)
answer = self.__socket.recv(1)
if answer == b'S':
# Success!
return self.__socket.read_fd()
elif answer == b'E':
# There was an error, read the error as well
error = self.__socket.recv(1)
errno = struct.unpack('i',
self.__read_all(len(struct.pack('i',
0))))
if error == b'S':
cause = 'socket'
elif error == b'B':
cause = 'bind'
else:
self.__socket = None
raise CreatorError('Unknown error cause' + str(answer), True)
raise CreatorError('Error creating socket on ' + cause, False,
errno[0])
else:
self.__socket = None
raise CreatorError('Unknown response ' + str(answer), True)
except socket.error as se:
self.__socket = None
raise CreatorError(str(se), True)
def __read_all(self, length):
"""
Keeps reading until length data is read or EOF or error happens.
EOF is considered error as well and throws.
"""
result = b''
while len(result) < length:
data = self.__socket.recv(length - len(result))
if len(data) == 0:
self.__socket = None
raise CreatorError('Unexpected EOF', True)
result += data
return result
......@@ -61,6 +61,7 @@ class FakeCreator:
raise InvalidPlan('Nothing more planned')
(kind, data) = self.__plan[0]
if kind == 'e':
self.__plan.pop(0)
raise socket.error('False socket error')
if kind != expected:
raise InvalidPlan('Planned ' + kind + ', but ' + expected +
......@@ -108,6 +109,7 @@ class FakeCreator:
self.__plan[0] = ('s', rest)
else:
self.__plan.pop(0)
def all_used(self):
"""
Returns if the whole plan was consumed.
......@@ -118,15 +120,65 @@ class ParserTests(unittest.TestCase):
"""
Testcases for the Parser class.
"""
def __terminate(self):
creator = FakeCreator([('s', b'T'), ('r', b'')])
parser = Parser(creator)
self.assertEqual(None, parser.terminate())
self.assertTrue(creator.all_used())
return parser
def test_terminate(self):
"""
Test if the command to terminate is correct and it waits for reading the
EOF.
"""
creator = FakeCreator([('s', b'T'), ('r', b'')])
self.__terminate()
def test_terminate_error1(self):
"""
Test it reports an exception when there's error terminating the creator.
This one raises an error when receiving the EOF.
"""
creator = FakeCreator([('s', b'T'), ('e', None)])
parser = Parser(creator)
self.assertEqual(None, parser.terminate())
self.assertTrue(creator.all_used())
with self.assertRaises(CreatorError) as cm:
parser.terminate()
self.assertTrue(cm.exception.fatal)
self.assertEqual(None, cm.exception.errno)
def test_terminate_error2(self):
"""
Test it reports an exception when there's error terminating the creator.
This one raises an error when sending data.
"""
creator = FakeCreator([('e', None)])
parser = Parser(creator)
with self.assertRaises(CreatorError) as cm:
parser.terminate()
self.assertTrue(cm.exception.fatal)
self.assertEqual(None, cm.exception.errno)
def test_terminate_twice(self):
"""
Test we can't terminate twice.
"""
parser = self.__terminate()
with self.assertRaises(CreatorError) as cm:
parser.terminate()
self.assertTrue(cm.exception.fatal)
self.assertEqual(None, cm.exception.errno)
def test_terminate_error3(self):
"""
Test it reports an exception when there's error terminating the creator.
This one sends data when it should have terminated.
"""
creator = FakeCreator([('s', b'T'), ('r', b'Extra data')])
parser = Parser(creator)
with self.assertRaises(CreatorError) as cm:
parser.terminate()
self.assertTrue(cm.exception.fatal)
self.assertEqual(None, cm.exception.errno)
def test_crash(self):
"""
......@@ -189,5 +241,31 @@ class ParserTests(unittest.TestCase):
self.__create('2001:db8::', socket.SOCK_STREAM,
b'T6\0\x2A\x20\x01\x0d\xb8\0\0\0\0\0\0\0\0\0\0\0\0')
def test_create_terminated(self):
"""
Test we can't request sockets after it was terminated.
"""
parser = self.__terminate()
with self.assertRaises(CreatorError) as cm:
parser.get_socket(IPAddr('0.0.0.0'), 0, 'UDP')
self.assertTrue(cm.exception.fatal)
self.assertEqual(None, cm.exception.errno)
def test_invalid_socktype(self):
"""
Test invalid socket type is rejected
"""
self.assertRaises(ValueError, Parser(FakeCreator([])).get_socket,
IPAddr('0.0.0.0'), 42, 'RAW')
def test_invalid_family(self):
"""
Test it rejects invalid address family.
"""
addr = IPAddr('0.0.0.0')
addr.family = 'Nonsense'
self.assertRaises(ValueError, Parser(FakeCreator([])).get_socket,
addr, 42, socket.SOCK_DGRAM)
if __name__ == '__main__':
unittest.main()
......@@ -3,7 +3,7 @@ The socket creator
The only thing we need higher rights than standard user is binding sockets to
ports lower than 1024. So we will have a separate process that keeps the
rights, while the rests drop them for security reasons.
rights, while the rest drops them for security reasons.
This process is the socket creator. Its goal is to be as simple as possible
and to contain as little code as possible to minimise the amount of code
......
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