Beispiel #1
0
class KytosServerProtocol(asyncio.Protocol):
    """Kytos' main request handler.

    It is instantiated once per connection between each switch and the
    controller.
    The setup method will dispatch a KytosEvent (``kytos/core.connection.new``)
    on the controller, that will be processed by a Core App.
    The finish method will close the connection and dispatch a KytosEvent
    (``kytos/core.connection.closed``) on the controller.
    """

    known_ports = {6633: 'openflow', 6653: 'openflow'}

    def __init__(self):
        """Initialize protocol and check if server attribute was set."""
        self._loop = asyncio.get_event_loop()

        self.connection = None
        self.transport = None
        self._rest = b''

        # server attribute is set outside this class, in KytosServer.init()
        # Here we initialize it to None to avoid pylint warnings
        if not getattr(self, 'server'):
            self.server = None

        # Then we check if it was really set
        if not self.server:
            raise ValueError("server instance must be assigned before init")

    def connection_made(self, transport):
        """Handle new client connection, passing it to the controller.

        Build a new Kytos `Connection` and send a ``kytos/core.connection.new``
        KytosEvent through the app buffer.
        """
        self.transport = transport

        addr, port = transport.get_extra_info('peername')
        _, server_port = transport.get_extra_info('sockname')
        socket = transport.get_extra_info('socket')

        LOG.info("New connection from %s:%s", addr, port)

        self.connection = Connection(addr, port, socket)

        # ASYNC TODO:
        # if self.server.protocol_name:
        #     self.known_ports[server_port] = self.server.protocol_name

        if server_port in self.known_ports:
            protocol_name = self.known_ports[server_port]
        else:
            protocol_name = f'{server_port:04d}'
        self.connection.protocol.name = protocol_name

        # ASYNC TODO:
        # self.request.settimeout(70)

        event_name = 'kytos/core.connection.new'
        # f'kytos/core.{self.connection.protocol.name}.connection.new'
        event = KytosEvent(name=event_name,
                           content={'source': self.connection})

        self._loop.create_task(self.server.controller.buffers.raw.aput(event))

    def data_received(self, data):
        """Handle each request and place its data in the raw event buffer.

        Sends the received binary data in a ``kytos/core.{protocol}.raw.in``
        event on the raw buffer.
        """
        data = self._rest + data

        LOG.debug("New data from %s:%s (%s bytes)", self.connection.address,
                  self.connection.port, len(data))

        # LOG.debug("New data from %s:%s (%s bytes): %s", self.addr, self.port,
        #           len(data), binascii.hexlify(data))

        content = {'source': self.connection, 'new_data': data}
        event_name = f'kytos/core.{self.connection.protocol.name}.raw.in'
        event = KytosEvent(name=event_name, content=content)

        self._loop.create_task(self.server.controller.buffers.raw.aput(event))

    def connection_lost(self, exc):
        """Close the connection socket and generate connection lost event.

        Emits a ``kytos/core.connection.lost`` event through the App buffer.
        """
        LOG.info("Connection lost with client %s:%s. Reason: %s",
                 self.connection.address, self.connection.port, exc)

        self.connection.close()

        content = {'source': self.connection}
        if exc:
            content['exception'] = exc
        event_name = \
            f'kytos/core.{self.connection.protocol.name}.connection.lost'
        event = KytosEvent(name=event_name, content=content)

        self._loop.create_task(self.server.controller.buffers.app.aput(event))
Beispiel #2
0
class KytosRequestHandler(BaseRequestHandler):
    """The socket/request handler class for our controller.

    It is instantiated once per connection between each switch and the
    controller.
    The setup method will dispatch a KytosEvent (``kytos/core.connection.new``)
    on the controller, that will be processed by a Core App.
    The finish method will close the connection and dispatch a KytonEvents
    (``kytos/core.connection.closed``) on the controller.
    """

    known_ports = {6633: 'openflow'}

    def __init__(self, request, client_address, server):
        """Contructor takes the parameters below.

        Args:
            request (socket.socket):
                Request sent by client.
            client_address (tuple):
                Client address, tuple with host and port.
            server (socketserver.BaseServer):
                Server used to send messages to client.
        """
        super().__init__(request, client_address, server)
        self.connection = None

    def setup(self):
        """Method used to setup the new connection.

        This method builds a new controller Connection, and places a
        ``kytos/core.connection.new`` KytosEvent in the app buffer.
        """
        self.ip = self.client_address[0]
        self.port = self.client_address[1]

        log.info("New connection from %s:%s", self.ip, self.port)

        self.connection = Connection(self.ip, self.port, self.request)  # noqa
        server_port = self.server.server_address[1]
        if server_port in self.known_ports:
            protocol_name = self.known_ports[server_port]
        else:
            protocol_name = f'{server_port:04d}'
        self.connection.protocol.name = protocol_name

        self.request.settimeout(30)
        self.exception = None

        event_name = \
            f'kytos/core.{self.connection.protocol.name}.connection.new'
        event = KytosEvent(name=event_name,
                           content={'source': self.connection})

        self.server.controller.buffers.app.put(event)

    def handle(self):
        """Handle each request and places its data in the raw event buffer.

        This method loops reading the binary data from the connection socket,
        and placing a ``kytos/core.messages.new`` KytosEvent in the raw event
        buffer.
        """
        curr_thread = current_thread()
        MAX_SIZE = 2**16
        while True:
            try:
                new_data = self.request.recv(MAX_SIZE)
            except (SocketError, OSError, InterruptedError,
                    ConnectionResetError) as exception:
                self.exception = exception
                log.debug('Socket handler exception while reading: %s',
                          exception)
                break
            if new_data == b'':
                self.exception = 'Request closed by client.'
                break

            if not self.connection.is_alive():
                continue

            log.debug("New data from %s:%s at thread %s", self.ip, self.port,
                      curr_thread.name)

            content = {'source': self.connection, 'new_data': new_data}
            event_name = \
                f'kytos/core.{self.connection.protocol.name}.raw.in'
            event = KytosEvent(name=event_name, content=content)

            self.server.controller.buffers.raw.put(event)

    def finish(self):
        """Method is called when the client connection is finished.

        This method closes the connection socket and generates a
        ``kytos/core.connection.lost`` KytosEvent in the App buffer.
        """
        log.info("Connection lost with Client %s:%s. Reason: %s", self.ip,
                 self.port, self.exception)
        self.connection.state = CONNECTION_STATE.FINISHED
        self.connection.close()
        content = {'source': self.connection}
        if self.exception:
            content['exception'] = self.exception
        event_name = \
            f'kytos/core.{self.connection.protocol.name}.connection.lost'
        event = KytosEvent(name=event_name, content=content)
        self.server.controller.buffers.app.put(event)
Beispiel #3
0
class TestConnection(TestCase):
    """Connection tests."""
    def setUp(self):
        """Instantiate a Connection."""
        socket = MagicMock()
        switch = MagicMock()
        self.connection = Connection('addr', 123, socket, switch)

        switch.connection = self.connection

    def test__str__(self):
        """Test __str__ method."""
        self.assertEqual(str(self.connection), "Connection('addr', 123)")

    def test__repr__(self):
        """Test __repr__ method."""
        self.connection.socket = 'socket'
        self.connection.switch = 'switch'

        expected = "Connection('addr', 123, 'socket', 'switch', " + \
                   "<ConnectionState.NEW: 0>)"
        self.assertEqual(repr(self.connection), expected)

    def test_state(self):
        """Test state property."""
        self.assertEqual(self.connection.state.value, 0)

        self.connection.state = ConnectionState.FINISHED
        self.assertEqual(self.connection.state.value, 4)

    def test_state__error(self):
        """Test state property to error case."""
        with self.assertRaises(Exception):
            self.connection.state = 1000

    def test_id(self):
        """Test id property."""
        self.assertEqual(self.connection.id, ('addr', 123))

    def test_send(self):
        """Test send method."""
        self.connection.send(b'data')

        self.connection.socket.sendall.assert_called_with(b'data')

    def test_send__error(self):
        """Test send method to error case."""
        self.connection.socket.sendall.side_effect = SocketError

        self.connection.send(b'data')

        self.assertIsNone(self.connection.socket)

    def test_close(self):
        """Test close method."""
        self.connection.close()

        self.assertIsNone(self.connection.socket)

    def test_close__os_error(self):
        """Test close method to OSError case."""
        self.connection.socket.shutdown.side_effect = OSError

        with self.assertRaises(OSError):
            self.connection.close()

        self.assertIsNotNone(self.connection.socket)

    def test_close__attribute_error(self):
        """Test close method to AttributeError case."""
        self.connection.socket = None

        self.connection.close()

        self.assertIsNone(self.connection.socket)

    def test_is_alive(self):
        """Test is_alive method to True and False returns."""
        self.assertTrue(self.connection.is_alive())

        self.connection.state = ConnectionState.FINISHED
        self.assertFalse(self.connection.is_alive())

    def test_is_new(self):
        """Test is_new method."""
        self.assertTrue(self.connection.is_new())

    def test_established_state(self):
        """Test set_established_state and is_established methods."""
        self.connection.set_established_state()
        self.assertTrue(self.connection.is_established())

    def test_setup_state(self):
        """Test set_setup_state and is_during_setup methods."""
        self.connection.set_setup_state()
        self.assertTrue(self.connection.is_during_setup())

    def test_update_switch(self):
        """Test update_switch method."""
        switch = MagicMock()
        self.connection.update_switch(switch)

        self.assertEqual(self.connection.switch, switch)
        self.assertEqual(switch.connection, self.connection)
Beispiel #4
0
class KytosServerProtocol(asyncio.Protocol):
    """Kytos' main request handler.

    It is instantiated once per connection between each switch and the
    controller.
    The setup method will dispatch a KytosEvent (``kytos/core.connection.new``)
    on the controller, that will be processed by a Core App.
    The finish method will close the connection and dispatch a KytosEvent
    (``kytos/core.connection.closed``) on the controller.
    """

    known_ports = {
        6633: 'openflow',
        6653: 'openflow'
    }

    def __init__(self):
        """Initialize protocol and check if server attribute was set."""
        self._loop = asyncio.get_event_loop()

        self.connection = None
        self.transport = None
        self._rest = b''

        # server attribute is set outside this class, in KytosServer.init()
        # Here we initialize it to None to avoid pylint warnings
        if not getattr(self, 'server'):
            self.server = None

        # Then we check if it was really set
        if not self.server:
            raise ValueError("server instance must be assigned before init")

    def connection_made(self, transport):
        """Handle new client connection, passing it to the controller.

        Build a new Kytos `Connection` and send a ``kytos/core.connection.new``
        KytosEvent through the app buffer.
        """
        self.transport = transport

        addr, port = transport.get_extra_info('peername')
        _, server_port = transport.get_extra_info('sockname')
        socket = transport.get_extra_info('socket')

        LOG.info("New connection from %s:%s", addr, port)

        self.connection = Connection(addr, port, socket)

        # This allows someone to inherit from KytosServer and start a server
        # on another port to handle a different protocol.
        if self.server.protocol_name:
            self.known_ports[server_port] = self.server.protocol_name

        if server_port in self.known_ports:
            protocol_name = self.known_ports[server_port]
        else:
            protocol_name = f'{server_port:04d}'
        self.connection.protocol.name = protocol_name

        event_name = f'kytos/core.{protocol_name}.connection.new'
        event = KytosEvent(name=event_name,
                           content={'source': self.connection})

        self._loop.create_task(self.server.controller.buffers.raw.aput(event))

    def data_received(self, data):
        """Handle each request and place its data in the raw event buffer.

        Sends the received binary data in a ``kytos/core.{protocol}.raw.in``
        event on the raw buffer.
        """
        # max_size = 2**16
        # new_data = self.request.recv(max_size)

        data = self._rest + data

        LOG.debug("New data from %s:%s (%s bytes)",
                  self.connection.address, self.connection.port, len(data))

        # LOG.debug("New data from %s:%s (%s bytes): %s", self.addr, self.port,
        #           len(data), binascii.hexlify(data))

        content = {'source': self.connection, 'new_data': data}
        event_name = f'kytos/core.{self.connection.protocol.name}.raw.in'
        event = KytosEvent(name=event_name, content=content)

        self._loop.create_task(self.server.controller.buffers.raw.aput(event))

    def connection_lost(self, exc):
        """Close the connection socket and generate connection lost event.

        Emits a ``kytos/core.{protocol}.connection.lost`` event through the
        App buffer.
        """
        reason = exc or "Request closed by client"
        LOG.info("Connection lost with client %s:%s. Reason: %s",
                 self.connection.address, self.connection.port, reason)

        self.connection.close()

        content = {'source': self.connection}
        if exc:
            content['exception'] = exc
        event_name = \
            f'kytos/core.{self.connection.protocol.name}.connection.lost'
        event = KytosEvent(name=event_name, content=content)

        self._loop.create_task(self.server.controller.buffers.app.aput(event))