def _set_policies( self, request, context ) -> Tuple[typing.List[peering_pb2.Policy], typing.List[str]]: # Delete old policies. Parse the new ones, and try saving them to the DB. # Returns the unsuccessful policies and matching error descriptions. # context.abort() is called on fatal errors to abort the RPC and trigger a transaction # rollback. asn_str, client = get_client_from_metadata( context.invocation_metadata()) asn = ASN(asn_str) # Delete previous policies if request.vlan: try: vlan_id = VLAN.objects.get(name=request.vlan).id except VLAN.DoesNotExist: context.abort(grpc.StatusCode.NOT_FOUND, "VLAN does not exist") _assert_policy_write_permission(context, asn, client, request.vlan) _delete_policies(asn, vlan_id) else: _assert_policy_write_permission(context, asn, client) _delete_policies(asn) # Create new policies rejected_policies = [] errors = [] for policy in request.policies: serializer = PolicyProtoSerializer(message=policy) if policy.asn != asn_str: rejected_policies.append(policy) errors.append("Policy ASN belongs to foreign AS") continue if request.vlan and policy.vlan != request.vlan: rejected_policies.append(policy) errors.append("VLAN excluded by filter") continue if not serializer.is_valid(): rejected_policies.append(policy) errors.append(_fmt_validation_errors(serializer.errors)) continue try: serializer.save() except ValidationError as e: msg, _ = _translate_validation_errors(e) rejected_policies.append(policy) errors.append(msg) continue # Update links and notify clients asys = AS.objects.get(asn=asn) for vlan in asys.get_connected_vlans(): policy_resolver.update_accepted_peers(vlan, asys) policy_resolver.update_links(vlan, asys) return rejected_policies, errors
def DestroyPolicy(self, request, context): """Delete a policy.""" asn_str, client = get_client_from_metadata( context.invocation_metadata()) asn = ASN(asn_str) if request.asn != asn_str: context.abort(grpc.StatusCode.PERMISSION_DENIED, "Cannot delete policies of other ASes") serializer = PolicyProtoSerializer(message=request) if not serializer.is_valid(): context.abort(grpc.StatusCode.INVALID_ARGUMENT, _fmt_validation_errors(serializer.errors)) try: policy = serializer.get() except ObjectDoesNotExist: context.abort(grpc.StatusCode.NOT_FOUND, "Policy does not exist") _assert_policy_write_permission(context, asn, client, request.vlan) policy.delete() # Update links and notify clients policy_resolver.update_accepted_peers(policy.vlan, policy.asys) policy_resolver.update_links(policy.vlan, policy.asys) return Empty()
def CreatePolicy(self, request, context): """Create a new policy.""" asn_str, client = get_client_from_metadata( context.invocation_metadata()) asn = ASN(asn_str) if request.asn != asn_str: context.abort(grpc.StatusCode.PERMISSION_DENIED, "Cannot create policies for other ASes") serializer = PolicyProtoSerializer(message=request) if not serializer.is_valid(): context.abort(grpc.StatusCode.INVALID_ARGUMENT, _fmt_validation_errors(serializer.errors)) _assert_policy_write_permission(context, asn, client, request.vlan) try: policy = serializer.save() except ValidationError as e: msg, code = _translate_validation_errors(e) context.abort(code, msg) # Update links and notify clients policy_resolver.update_accepted_peers(policy.vlan, policy.asys) policy_resolver.update_links(policy.vlan, policy.asys) return serializer.message
def ListPolicies(self, request, context): """List policies of the AS making the request.""" asn_str, client = get_client_from_metadata( context.invocation_metadata()) asn = ASN(asn_str) # Prepare common selection criteria common_selection = {'asys__asn': asn} if request.vlan: common_selection['vlan__name'] = request.vlan if request.asn and request.asn != asn_str: context.abort(grpc.StatusCode.PERMISSION_DENIED, "Cannot list policies of other ASes") if request.WhichOneof("accept_") is not None: common_selection['accept'] = request.accept # Return matching default policies if request.WhichOneof('peer') is None or request.peer_everyone: for policy in DefaultPolicy.objects.filter(**common_selection): yield PolicyProtoSerializer(policy).message # Return matching AS policies if request.WhichOneof('peer') is None or request.peer_asn: policies = AsPeerPolicy.objects.filter(**common_selection) if request.peer_asn: try: policies = policies.filter( peer_as__asn=ASN(request.peer_asn)) except ValueError: context.abort(grpc.StatusCode.INVALID_ARGUMENT, "Invalid ASN") for policy in policies.filter(**common_selection): yield PolicyProtoSerializer(policy).message # Return matching owner policies if request.WhichOneof('peer') is None or request.peer_owner: policies = OwnerPeerPolicy.objects.filter(**common_selection) if request.peer_owner: policies = policies.filter(peer_owner__name=request.peer_owner) for policy in policies.filter(**common_selection): yield PolicyProtoSerializer(policy).message # Return matching ISD policies if request.WhichOneof('peer') is None or request.peer_isd: policies = IsdPeerPolicy.objects.filter(**common_selection) if request.peer_isd: try: policies = policies.filter( peer_isd__isd_id=int(request.peer_isd)) except ValueError: context.abort(grpc.StatusCode.INVALID_ARGUMENT, "Invalid ISD") for policy in policies.filter(**common_selection): yield PolicyProtoSerializer(policy).message
def SetPortRange(self, request, context): """Set the UDP port range used for SCION underlay connections.""" asn_str, _ = get_client_from_metadata(context.invocation_metadata()) asn = ASN(asn_str) # Validate arguments and retrieve the interface try: vlan = VLAN.objects.get(name=request.interface_vlan) except VLAN.DoesNotExist: context.abort(grpc.StatusCode.NOT_FOUND, "VLAN does not exist") try: ip = ipaddress.ip_address(request.interface_ip) except ValueError: context.abort(grpc.StatusCode.INVALID_ARGUMENT, "Invalid IP address") try: interface = Interface.objects.get(vlan=vlan, public_ip=ip) except (Interface.DoesNotExist, Interface.MultipleObjectsReturned): context.abort(grpc.StatusCode.NOT_FOUND, "Interface not found") recreate_links = not (request.first_port <= interface.first_port and request.last_port >= interface.last_port) try: interface.first_port = request.first_port interface.last_port = request.last_port interface.save() except ValidationError: context.abort(grpc.StatusCode.INVALID_ARGUMENT, "Invalid port range") if recreate_links: # Recreate the interface's links with the new ports for link in interface.query_links().all(): link.delete() policy_resolver.update_links(vlan, AS.objects.get(asn=asn)) return peering_pb2.google_dot_protobuf_dot_empty__pb2.Empty()
def StreamChannel(self, request_iterator, context): """Server side of the persistent bidirectional gRPC stream peering clients maintain with the coordinator. Since bidirectional gRPC streams in Python use blocking generators for sending and receiving, an additional thread just for reading request from the stream is created for every connection. The request listener threads forwards received requests to the main thread handling the connection via the associated ClientConnection object. """ asn, client_name = get_client_from_metadata( context.invocation_metadata()) asn = ASN(asn) # Register the connection try: conn = ClientRegistry.createConnection(asn, client_name) except KeyError as e: context.abort(grpc.StatusCode.NOT_FOUND, str(e)) except ClientConnections.AlreadyConnected as e: context.abort(grpc.StatusCode.ALREADY_EXISTS, str(e)) except: context.abort(grpc.StatusCode.INTERNAL, "Internal error") # Enqueue link create messages for all existing links. try: client = PeeringClient.objects.get(asys__asn=asn, name=client_name) for interface in Interface.objects.filter( peering_client=client).all(): for link in interface.query_links().all(): if link.interface_a == interface: update = create_link_update( peering_pb2.LinkUpdate.Type.CREATE, link_type=link.link_type, local_interface=link.interface_a, local_port=link.port_a, remote_interface=link.interface_b, remote_port=link.port_b) else: update = create_link_update( peering_pb2.LinkUpdate.Type.CREATE, link_type=link.link_type, local_interface=link.interface_b, local_port=link.port_b, remote_interface=link.interface_a, remote_port=link.port_a) conn.send_link_update(update) except: ClientRegistry.destroyConnection(conn) raise # Launch a new thread to listen for requests from the client. def stream_listener(): try: for request in request_iterator: conn.stream_request_received(request) except grpc.RpcError: pass finally: conn.request_stream_closed() listener = threading.Thread( target=stream_listener, name="gRPC stream listener for {}-{}".format(asn, client_name)) listener.start() # Run the event loop to process requests and generate responses. try: for response in conn.run(): yield response finally: ClientRegistry.destroyConnection(conn) listener.join()