Exemple #1
0
    def handle_getaclstate(self, message, sock):
        """
        Handles a GETACLSTATE response.

        Currently this is basically a no-op. We log on errors, but can't do
        anything about them.
        """
        log.debug("Received GETACLSTATE response: %s", message.fields)

        try:
            try:
                return_code = message.fields['rc']
            except KeyError:
                raise InvalidRequest("Missing \"rc\" field", message.fields)

            try:
                return_str = message.fields['message']
            except KeyError:
                raise InvalidRequest("Missing \"message\" field",
                                     message.fields)

            if return_code != RC_SUCCESS:
                #*************************************************************#
                #* It's hard to see what errors we might get other than a    *#
                #* timing window one of "never heard of that endpoint". We   *#
                #* just log it and continue onwards.                         *#
                #*************************************************************#
                log.error("ACL state request refused with rc : %s, %s",
                          return_code, return_str)

        except InvalidRequest as error:
            log.error(
                "Got invalid GETACLSTATE response : %s, "
                "request fields %s", error.message, error.fields)
Exemple #2
0
    def handle_resyncstate(self, message, sock):
        """
        Handles a RESYNCSTATE response.

        If the response is an error, abandon the resync. Otherwise, if we
        expect no endpoints we're done. Otherwise, set the expected number of
        endpoints.
        """
        log.debug("Received resync response: %s", message.fields)
        try:
            try:
                endpoint_count = int(message.fields['endpoint_count'])
            except KeyError:
                raise InvalidRequest("Missing \"endpoint_count\" field",
                                     message.fields)
            try:
                return_code = message.fields['rc']
            except KeyError:
                raise InvalidRequest("Missing \"rc\" field", message.fields)
            try:
                return_str = message.fields['message']
            except KeyError:
                raise InvalidRequest("Missing \"message\" field",
                                     message.fields)
            try:
                self.iface_prefix = message.fields['interface_prefix']
            except KeyError:
                raise InvalidRequest("Missing \"interface_prefix\" field",
                                     message.fields)
        except InvalidRequest as error:
            log.error(
                "Got invalid RESYNCSTATE response : %s, "
                "request fields %s", error.message, error.fields)
            self.complete_endpoint_resync(False)
            return

        if return_code != RC_SUCCESS:
            log.error('Resync request refused with rc : %s, %s', return_code,
                      return_str)
            self.complete_endpoint_resync(False)
            return

        # Reset / create the global rules.
        frules.set_global_rules(self.config, self.iface_prefix,
                                self.iptables_state)

        # If there are no endpoints to expect, or we got this after all the
        # resyncs, then we're done.
        if endpoint_count == 0 or endpoint_count == self.resync_recd:
            self.complete_endpoint_resync(True)
            return

        self.resync_expected = endpoint_count
        return
Exemple #3
0
    def store_update(self, fields):
        """
        Update an endpoint's MAC, state and addresses with the contents of an
        ENDPOINT* API message.

        :param fields: dictionary of Endpoint Update API fields.
        :return: None.
        :throws: InvalidRequest if unsuccessful.
        """

        try:
            mac = fields['mac']
        except KeyError:
            raise InvalidRequest('Missing "mac" field', fields)

        try:
            state = fields['state']
        except KeyError:
            raise InvalidRequest('Missing "state" field', fields)

        try:
            addrs = fields['addrs']
        except KeyError:
            raise InvalidRequest('Missing "addrs" field', fields)

        addresses = {}

        for addr in addrs:
            try:
                address = Address(addr)
            except InconsistentIPVersion as err:
                # exception has an operator-suitable error message.
                log.error("For endpoint %s, %s", self.uuid, err)
                raise InvalidRequest(str(err), fields)
            except InvalidAddress:
                log.error("Invalid address for endpoint %s : %s", self.uuid,
                          fields)
                raise InvalidRequest("Invalid address for endpoint", fields)

            if address.ip in addresses:
                # The IP is listed multiple times in the message.  This is
                # an error.
                log.error(
                    "IP %s listed multiple times in message for endpoint"
                    " %s : %s", address.ip, self.uuid, fields)
                raise InvalidRequest(
                    "IP %s listed multiple times in message "
                    "for endpoint %s" % (address.ip, self.uuid), fields)

            # All error checks passed on this addr.
            addresses[address.ip] = address

        if state not in Endpoint.STATES:
            log.error("Invalid state %s for endpoint %s : %s", state,
                      self.uuid, fields)
            raise InvalidRequest('Invalid state "%s"' % state, fields)

        self.addresses = addresses
        self.mac = mac.encode('ascii')
        self.state = state.encode('ascii')
Exemple #4
0
    def handle_endpointdestroyed(self, message, sock):
        """
        Handles an ENDPOINTDESTROYED message.

        ENDPOINTDESTROYED is an active notification that an endpoint is going
        away.
        """
        log.debug("Received endpoint destroy: %s", message.fields)

        # Initially assume success.
        fields = {"rc": RC_SUCCESS, "message": ""}

        try:
            # Get the endpoint ID from the message.
            try:
                delete_id = message.fields['endpoint_id']
            except KeyError:
                raise InvalidRequest("Missing \"endpoint_id\" field",
                                     message.fields)
            try:
                # Remove this endpoint from Felix's list of managed
                # endpoints.
                endpoint = self.endpoints.pop(delete_id)
            except KeyError:
                log.error("Received destroy for absent endpoint %s", delete_id)
                fields = {
                    "rc": RC_NOTEXIST,
                    "message": "Endpoint %s does not exist" % delete_id,
                }
            else:
                # Unsubscribe from ACL information for this endpoint.
                self.sockets[Socket.TYPE_ACL_SUB].unsubscribe(
                    delete_id.encode('utf-8'))

                # Remove programming for this endpoint.
                endpoint.remove(self.iptables_state)

        except InvalidRequest as error:
            fields = {
                "rc": RC_INVALID,
                "message": error.message,
            }
            log.error(
                "Got invalid ENDPOINTDESTROYED message : %s, "
                "request fields %s", error.message, error.fields)

        # Send the response.
        sock.send(Message(Message.TYPE_EP_RM, fields))
Exemple #5
0
    def handle_endpointupdated(self, message, sock):
        """
        Handles an ENDPOINTUPDATED message.

        This has very similar logic to ENDPOINTCREATED, but does not actually
        create new endpoints.
        """
        log.debug("Received endpoint update: %s", message.fields)

        # Initially assume success.
        fields = {"rc": RC_SUCCESS, "message": ""}

        try:
            # Get the endpoint ID from the message.
            try:
                endpoint_id = message.fields['endpoint_id']
            except KeyError:
                raise InvalidRequest("Missing \"endpoint_id\" field",
                                     message.fields)

            try:
                # Update the endpoint
                endpoint = self.endpoints[endpoint_id]

            except KeyError:
                log.error("Received update for absent endpoint %s",
                          endpoint_id)

                fields = {
                    "rc": RC_NOTEXIST,
                    "message": "Endpoint %s does not exist" % endpoint_id,
                }

            else:
                # Update the endpoint state; this can fail with InvalidRequest.
                self._update_endpoint(endpoint, message.fields)

        except InvalidRequest as error:
            fields = {
                "rc": RC_INVALID,
                "message": error.message,
            }
            log.error(
                "Got invalid ENDPOINTUPDATED message : %s, "
                "request fields %s", error.message, error.fields)

        # Send the response.
        sock.send(Message(Message.TYPE_EP_UP, fields))
Exemple #6
0
    def run(self):
        """
        Executes one iteration of the main agent loop.
        """
        # Issue a poll request on all active sockets.
        endpoint_resync_needed = False
        acl_resync_needed = False

        if self.iface_prefix:
            poll_list = self.sockets.values()
        else:
            # Not got an first resync response (as no interface prefix), so
            # ignore all sockets except the EP_REQ socket until we do.
            poll_list = [self.sockets[Socket.TYPE_EP_REQ]]

        active_sockets = fsocket.poll(poll_list, self.config.EP_RETRY_INT_MS)

        # For each active socket, pull the message off and handle it.
        for sock in active_sockets:
            message = sock.receive()

            if message is not None:
                try:
                    self.handlers[message.type](message, sock)
                except KeyError:
                    # We are going down, but raise a better exception.
                    raise InvalidRequest("Unrecognised message type",
                                         message.fields)

        for sock in self.sockets.values():
            #*****************************************************************#
            #* See if anything else is required on this socket. First, check *#
            #* whether any have timed out.  A timed out socket needs to be   *#
            #* reconnected. Also, whatever API it belongs to needs to be     *#
            #* resynchronised.                                               *#
            #*****************************************************************#
            if sock.timed_out():
                log.error("Timed out remote entity : %s", sock.descr)

                #*************************************************************#
                #* If we lost the connection on which we would receive       *#
                #* ENDPOINTCREATED messages, we need to trigger a total      *#
                #* endpoint resync, and similarly for ACLs if we have lost   *#
                #* the connection on which we would receive ACLUPDATE        *#
                #* messages.                                                 *#
                #*************************************************************#
                if sock.type == Socket.TYPE_EP_REP:
                    #*********************************************************#
                    #* We lost the connection on which we would receive      *#
                    #* ENDPOINTCREATED messages. We may be out of step, so   *#
                    #* need a total endpoint update.                         *#
                    #*********************************************************#
                    endpoint_resync_needed = True
                elif (sock.type == Socket.TYPE_ACL_SUB
                      or sock.type == Socket.TYPE_ACL_REQ):
                    #*********************************************************#
                    #* We lost the connection on which we would receive      *#
                    #* ACLUPDATE messages, or might have lost some queued    *#
                    #* GETACLSTATE messages. We may be out of step, so we    *#
                    #* need a total ACL resync.                              *#
                    #*********************************************************#
                    acl_resync_needed = True

                if (self.resync_id is not None and sock.type
                        in (Socket.TYPE_EP_REQ, Socket.TYPE_EP_REP)):
                    #*********************************************************#
                    #* A resync was in progress, but we may have lost the    *#
                    #* RESYNCSTATE request or response or (potentially) an   *#
                    #* ENDPOINTCREATED message due to a lost                 *#
                    #* connection. That means we have to give up on this     *#
                    #* resync, tidy up and retry.                            *#
                    #*********************************************************#
                    self.complete_endpoint_resync(False)
                    endpoint_resync_needed = True

                # Recreate the socket.
                sock.restart(self.hostname, self.zmq_context)

        # Now, check if we need to resynchronize and do it.
        if (self.resync_id is None and (futils.time_ms() - self.resync_time >
                                        self.config.RESYNC_INT_SEC * 1000)):
            # Time for a total resync of all endpoints
            endpoint_resync_needed = True

        if endpoint_resync_needed:
            self.resync_endpoints()
        elif acl_resync_needed:
            #*****************************************************************#
            #* Note that an endpoint resync implicitly involves an ACL       *#
            #* resync, so there is no point in triggering one when an        *#
            #* endpoint resync has just started (as opposed to when we are   *#
            #* in the middle of an endpoint resync and just lost our         *#
            #* connection).                                                  *#
            #*****************************************************************#
            self.resync_acls()

        # We are not about to send any more messages; send any required
        # keepalives.
        for sock in self.sockets.values():
            sock.keepalive()

        #*********************************************************************#
        #* Finally, retry any endpoints which need retrying. We remove them  *#
        #* from ep_retry if they no longer exist or if the retry succeeds;   *#
        #* the simplest way to do this is to copy the list, clear ep_retry   *#
        #* then add them back if necessary.                                  *#
        #*********************************************************************#
        retry_list = list(self.ep_retry)
        self.ep_retry.clear()
        for uuid in retry_list:
            if uuid in self.endpoints:
                endpoint = self.endpoints[uuid]
                log.debug("Retry program of %s" % endpoint.suffix)
                if endpoint.program_endpoint(self.iptables_state):
                    # Failed again - put back on list
                    self.ep_retry.add(uuid)
                else:
                    # Programmed OK, so apply any ACLs we might have.
                    endpoint.update_acls()
            else:
                log.debug("No retry programming %s - no longer exists" % uuid)
Exemple #7
0
    def handle_endpointcreated(self, message, sock):
        """
        Handles an ENDPOINTCREATED message.

        ENDPOINTCREATED can be received in two cases: either as part of a
        state resynchronization, or to notify Felix of a new endpoint to
        manage.
        """
        log.debug("Received endpoint create: %s", message.fields)

        # Default to success
        fields = {"rc": RC_SUCCESS, "message": ""}

        try:
            try:
                endpoint_id = message.fields['endpoint_id']
            except KeyError:
                raise InvalidRequest("Missing \"endpoint_id\" field",
                                     message.fields)
            try:
                mac = message.fields['mac']
            except KeyError:
                raise InvalidRequest("Missing \"mac\" field", message.fields)

            try:
                resync_id = message.fields['resync_id']
            except KeyError:
                raise InvalidRequest("Missing \"resync_id\" field",
                                     message.fields)

            try:
                interface = message.fields['interface_name']
            except KeyError:
                raise InvalidRequest("Missing \"interface_name\" field",
                                     message.fields)

            if not interface.startswith(self.iface_prefix):
                raise InvalidRequest(
                    "Interface \"%s\" does not start with \"%s\"" %
                    (interface, self.iface_prefix), message.fields)

            endpoint = self.endpoints.get(endpoint_id)
            if endpoint is not None and resync_id is None:
                # We know about this endpoint, but not a resync; accept, but log.
                log.warning(
                    "Received endpoint creation for existing endpoint %s",
                    endpoint_id)
            elif endpoint is not None and resync_id is not None:
                # We know about this endpoint, and this is a resync.
                endpoint.pending_resync = False
            elif endpoint is None:
                # New endpoint.
                endpoint = self._create_endpoint(endpoint_id, mac, interface)

            # Update the endpoint state; this can fail with Invalid Request.
            self._update_endpoint(endpoint, message.fields)

            if resync_id:
                # This endpoint created was part of a resync.
                if resync_id == self.resync_id:
                    #*********************************************************#
                    #* It was part of the most recent resync.  Increment how *#
                    #* many ENDPOINTCREATED requests we have received, and   *#
                    #* if this is the last one expected, complete the        *#
                    #* resync.                                               *#
                    #*********************************************************#
                    self.resync_recd += 1
                    if self.resync_expected is None:
                        # resync_expected not set - resync response pending
                        log.debug(
                            "Received ENDPOINTCREATED number %d for resync "
                            "before resync response", self.resync_recd)
                    else:
                        log.debug(
                            "Received ENDPOINTCREATED for resync, %d out of %d",
                            self.resync_recd, self.resync_expected)

                    if self.resync_recd == self.resync_expected:
                        self.complete_endpoint_resync(True)
                else:
                    #*********************************************************#
                    #* We just got an ENDPOINTCREATED for the wrong          *#
                    #* resync. This can happen (perhaps we restarted during  *#
                    #* a resync and are seeing messages from that old        *#
                    #* resync).  Log it though, since this is very unusual   *#
                    #* and strange.                                          *#
                    #*********************************************************#
                    log.warning(
                        "Received ENDPOINTCREATED for %s with invalid "
                        "resync %s (expected %s)", endpoint_id, resync_id,
                        self.resync_id)

        except InvalidRequest as error:
            fields = {
                "rc": RC_INVALID,
                "message": error.message,
            }
            log.error(
                "Got invalid ENDPOINTCREATED message : %s, "
                "request fields %s", error.message, error.fields)

        # Send the response.
        sock.send(Message(Message.TYPE_EP_CR, fields))