Inter-Process Communication mit gRPC

Auf demselben Computer ausgeführte Prozesse können so konzipiert werden, dass sie miteinander kommunizieren. Betriebssysteme bieten Technologien für die schnelle und effiziente Inter-Process Communication (IPC). Beliebte Beispiele für IPC-Technologien sind Unix-Domänensockets und Named Pipes.

.NET bietet Unterstützung für die Inter-Process Communication mithilfe von gRPC.

Für die integrierte Unterstützung für Named Pipes in ASP.NET Core ist .NET 8 oder höher erforderlich.

Erste Schritte

IPC-Aufrufe werden von einem Client an einen Server gesendet. Damit die Kommunikation zwischen Apps auf einem Computer mit gRPC funktioniert, muss mindestens eine App einen ASP.NET Core-gRPC-Server hosten.

Ein ASP.NET Core-gRPC-Server wird normalerweise aus der gRPC-Vorlage erstellt. Die von der Vorlage erstellte Projektdatei verwendet Microsoft.NET.SDK.Web als SDK:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <ItemGroup>
    <PackageReference Include="Grpc.AspNetCore" Version="2.47.0" />
    <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
  </ItemGroup>

</Project>

Der Microsoft.NET.SDK.Web SDK-Wert fügt automatisch einen Verweis auf das ASP.NET Core-Framework hinzu. Der Verweis ermöglicht der App die Verwendung von ASP.NET Core-Typen, die zum Hosten eines Servers erforderlich sind.

Es ist auch möglich, vorhandenen Projekten, in denen nicht ASP.NET Core verwendet wird (z. B. Windows-Dienste, WPF-Apps oder WinForms-Apps), einen Server hinzuzufügen. Weitere Informationen finden Sie unter Hosten von gRPC in Projekten ohne ASP.NET Core.

IPC-Transporte (Inter-Process Communication)

gRPC-Aufrufe zwischen einem Client und einem Server auf verschiedenen Computern werden in der Regel über TCP-Sockets gesendet. TCP ist eine gute Wahl für die Kommunikation über ein Netzwerk oder das Internet. IPC-Transporte bieten jedoch Vorteile bei der Kommunikation zwischen Prozessen auf demselben Computer:

  • Weniger Aufwand und schnellere Übertragungsgeschwindigkeiten.
  • Integration in Betriebssystemsicherheitsfunktionen.
  • Verwendet keine TCP-Ports, bei denen es sich um eine begrenzte Ressource handelt.

.NET unterstützt mehrere IPC-Transporte:

Je nach Betriebssystem können plattformübergreifende Apps unterschiedliche IPC-Transporte auswählen. Eine App kann das Betriebssystem beim Start überprüfen und den gewünschten Transport für diese Plattform auswählen:

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
    if (OperatingSystem.IsWindows())
    {
        serverOptions.ListenNamedPipe("MyPipeName");
    }
    else
    {
        var socketPath = Path.Combine(Path.GetTempPath(), "socket.tmp");
        serverOptions.ListenUnixSocket(socketPath);
    }

    serverOptions.ConfigureEndpointDefaults(listenOptions =>
    {
        listenOptions.Protocols = HttpProtocols.Http2;
    });
});

Sicherheitsüberlegungen

IPC-Apps senden und empfangen RPC-Aufrufe. Die externe Kommunikation ist ein potenzieller Angriffsvektor für IPC-Apps und muss ausreichend geschützt werden.

Schützen der IPC-Server-App gegen unerwartete Aufrufer

Die IPC-Server-App hostet RPC-Dienste, die von anderen Apps aufgerufen werden können. Eingehende Aufrufer sollten authentifiziert werden, um zu verhindern, dass nicht vertrauenswürdige Clients RPC-Aufrufe an den Server durchführen.

Die Transportsicherheit stellt eine Option dar, um einen Server zu schützen. IPC-Transporte wie Unix-Domänensockets und Named Pipes unterstützen das Einschränken des Zugriffs basierend auf Betriebssystemberechtigungen:

  • Named Pipes unterstützen das Schützen einer Pipe mit dem Windows-Zugriffssteuerungsmodell. Zugriffsrechte können in .NET konfiguriert werden, wenn ein Server mit der PipeSecurity-Klasse gestartet wird.
  • Unix-Domänensockets unterstützen das Schützen eines Sockets mit Dateiberechtigungen.

Eine weitere Möglichkeit zum Schützen eines IPC-Servers besteht darin, die Authentifizierungs- und Autorisierungsfeatures zu verwenden, die in ASP.NET Core integriert ist. Beispielsweise könnte der Server so konfiguriert werden, dass eine Zertifikatauthentifizierung erforderlich ist. RPC-Aufrufe durch Client-Apps ohne das erforderliche Zertifikat schlagen mit der Antwort „Nicht autorisiert“ fehl.

Überprüfen des Servers in der IPC-Client-App

Es ist wichtig, dass die Client-App die Identität des Servers überprüft, den sie aufruft. Die Validierung ist erforderlich, um die App davor zu schützen, dass böswillige Akteur*innen den vertrauenswürdigen Server stoppen, eigene Server ausführen und eingehende Daten von Clients akzeptieren.

Named Pipes unterstützen das Abrufen des Kontos, unter dem ein Server ausgeführt wird. Ein Client kann überprüfen, ob der Server durch das erwartete Konto gestartet wurde:

internal static bool CheckPipeConnectionOwnership(
    NamedPipeClientStream pipeStream, SecurityIdentifier expectedOwner)
{
    var remotePipeSecurity = pipeStream.GetAccessControl();
    var remoteOwner = remotePipeSecurity.GetOwner(typeof(SecurityIdentifier));
    return expectedOwner.Equals(remoteOwner);
}

Eine weitere Option zum Überprüfen des Servers ist das Schützen der Serverendpunkte mit HTTPS in ASP.NET Core. Der Client kann den SocketsHttpHandler so konfigurieren, dass dieser überprüft, ob der Server das erwartete Zertifikat verwendet, wenn die Verbindung hergestellt wird.

var socketsHttpHandler = new SocketsHttpHandler()
{
    SslOptions = new SslOptions()
    {
        RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
        {
            if (sslPolicyErrors != SslPolicyErrors.None)
            {
                return false;
            }

            // Validate server cert thumbprint matches the expected thumbprint.
        }
    }
};

Schützen vor einer Rechteausweitung der Named Pipe

Named Pipes unterstützen ein Feature namens Identitätswechsel. Mithilfe des Identitätswechsels kann der Server mit Named Pipes Code mit den Berechtigungen von Clientbenutzer*innen ausführen. Dies ist ein nützliches Feature, das jedoch zulassen kann, dass ein Server mit niedriger Berechtigung die Identität mit einem Aufrufer mit erhöhten Rechten wechseln und dann schädlichen Code ausführen kann.

Clients können vor diesem Angriff schützen, indem sie den Identitätswechsel beim Herstellen einer Verbindung zu einem Server nicht zulassen. Beim Erstellen einer Clientverbindung sollte ein TokenImpersonationLevel-Wert von None oder Anonymous verwendet werden, sofern kein anderer Wert für den Server erforderlich ist:

using var pipeClient = new NamedPipeClientStream(
    serverName: ".", pipeName: "testpipe", PipeDirection.In, PipeOptions.None, TokenImpersonationLevel.None);
await pipeClient.ConnectAsync();

TokenImpersonationLevel.None ist der Standardwert in NamedPipeClientStream-Konstruktoren, die keinen impersonationLevel-Parameter aufweisen.

Konfigurieren von Client und Server

Client und Server müssen für die Verwendung eines IPC-Transports (Inter-Process Communication) konfiguriert werden. Weitere Informationen zum Konfigurieren von Kestrel und SocketsHttpHandler für die Verwendung von IPC finden Sie hier:

Hinweis

Für die integrierte Unterstützung für Named Pipes in ASP.NET Core ist .NET 8 oder höher erforderlich.

Auf demselben Computer ausgeführte Prozesse können so konzipiert werden, dass sie miteinander kommunizieren. Betriebssysteme bieten Technologien für die schnelle und effiziente Inter-Process Communication (IPC). Beliebte Beispiele für IPC-Technologien sind Unix-Domänensockets und Named Pipes.

.NET bietet Unterstützung für die Inter-Process Communication mithilfe von gRPC.

Hinweis

Für die integrierte Unterstützung für Named Pipes in ASP.NET Core ist .NET 8 oder höher erforderlich.
Weitere Informationen finden Sie unter der .NET 8- oder höhen Version dieses Themas

Erste Schritte

gRPC-Aufrufe werden von einem Client an einen Server gesendet. Damit die Kommunikation zwischen Apps auf einem Computer mit gRPC funktioniert, muss mindestens eine App einen ASP.NET Core-gRPC-Server hosten.

ASP.NET Core und gRPC können in einer beliebigen App mit .NET Core 3.1 oder höher gehostet werden, indem sie dem Projekt das Microsoft.AspNetCore.App-Framework hinzufügen.

<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Grpc.AspNetCore" Version="2.47.0" />
    <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
  </ItemGroup>

</Project>

Die obige Projektdatei:

  • Fügt einen Frameworkverweis auf Microsoft.AspNetCore.App hinzu Der Frameworkverweis ermöglicht es Apps, die nicht unter ASP.NET Core laufen (z. B. Windows-Dienste, WPF-Apps oder WinForms-Apps), ASP.NET Core zu verwenden und einen ASP.NET Core-Server zu hosten.
  • Fügt einen NuGet-Paketverweis auf Grpc.AspNetCore hinzu
  • Fügt eine .proto-Datei hinzu

Konfigurieren von Unix-Domänensockets

gRPC-Aufrufe zwischen einem Client und einem Server auf verschiedenen Computern werden in der Regel über TCP-Sockets gesendet. TCP wurde für die Kommunikation über ein Netzwerk entwickelt. Unix-Domänensockets (UDS) sind eine breit unterstützte IPC-Technologie, die effizienter ist als TCP, wenn der Client und der Server auf demselben Computer sind. .NET bietet integrierte Unterstützung für UDS in Client- und Server-Apps.

Anforderungen:

Serverkonfiguration

Unix-Domänensockets (UDS) werden von Kestrel unterstützt, was in Program.cs konfiguriert wird:

public static readonly string SocketPath = Path.Combine(Path.GetTempPath(), "socket.tmp");

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
            webBuilder.ConfigureKestrel(options =>
            {
                if (File.Exists(SocketPath))
                {
                    File.Delete(SocketPath);
                }
                options.ListenUnixSocket(SocketPath, listenOptions =>
                {
                    listenOptions.Protocols = HttpProtocols.Http2;
                });
            });
        });

Für das vorherige Beispiel gilt Folgendes:

  • Der Endpunkt von Kestrel wird in ConfigureKestrel konfiguriert.
  • ListenUnixSocket wird aufgerufen, um dem UDS mit dem angegebenen Pfad zu laschen.
  • Erstellt einen UDS-Endpunkt, der nicht für die Verwendung von HTTPS konfiguriert ist. Informationen zum Aktivieren von HTTPS finden Sie unter Kestrel-HTTPS-Endpunktkonfiguration.

Clientkonfiguration

GrpcChannel unterstützt gRPC-Aufrufe für benutzerdefinierte Datentransporte. Wenn ein Kanal erstellt wird, kann er mit einer SocketsHttpHandler-Klasse konfiguriert werden, die über ein benutzerdefiniertes ConnectCallback-Objekt verfügt. Der Rückruf ermöglicht es dem Client, Verbindungen für benutzerdefinierte Datentransporte herzustellen und dann HTTP-Anforderungen über diese Datentransporte zu senden.

Beispiel für eine UDS-Verbindungsfactory (Unix Domain Socket):

public class UnixDomainSocketConnectionFactory
{
    private readonly EndPoint _endPoint;

    public UnixDomainSocketConnectionFactory(EndPoint endPoint)
    {
        _endPoint = endPoint;
    }

    public async ValueTask<Stream> ConnectAsync(SocketsHttpConnectionContext _,
        CancellationToken cancellationToken = default)
    {
        var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);

        try
        {
            await socket.ConnectAsync(_endPoint, cancellationToken).ConfigureAwait(false);
            return new NetworkStream(socket, true);
        }
        catch
        {
            socket.Dispose();
            throw;
        }
    }
}

So wird die benutzerdefinierte Verbindungsfactory zum Erstellen eines Kanals verwendet:

public static readonly string SocketPath = Path.Combine(Path.GetTempPath(), "socket.tmp");

public static GrpcChannel CreateChannel()
{
    var udsEndPoint = new UnixDomainSocketEndPoint(SocketPath);
    var connectionFactory = new UnixDomainSocketConnectionFactory(udsEndPoint);
    var socketsHttpHandler = new SocketsHttpHandler
    {
        ConnectCallback = connectionFactory.ConnectAsync
    };

    return GrpcChannel.ForAddress("http://localhost", new GrpcChannelOptions
    {
        HttpHandler = socketsHttpHandler
    });
}

Kanäle, die mithilfe des vorangehenden Codes erstellt werden, senden gRPC-Aufrufe über Unix Domain Sockets (UDS). Unterstützung für andere IPC-Technologien kann mithilfe der Erweiterbarkeit in Kestrel und SocketsHttpHandler implementiert werden.