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)
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
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')
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))
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))
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)
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))