예제 #1
0
    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
예제 #2
0
    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()
예제 #3
0
    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
예제 #4
0
    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
예제 #5
0
    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()
예제 #6
0
    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()