Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ let dependencies: [Package.Dependency] = [

// This adds some build settings which allow us to map "@available(gRPCSwiftNIOTransport 2.x, *)" to
// the appropriate OS platforms.
let nextMinorVersion = 9
let nextMinorVersion = 10
let availabilitySettings: [SwiftSetting] = (0 ... nextMinorVersion).map { minor in
let name = "gRPCSwiftNIOTransport"
let version = "2.\(minor)"
Expand Down Expand Up @@ -100,6 +100,16 @@ let targets: [Target] = [
]
),

// C-module exposing Linux declarations which glibc guards behind `_GNU_SOURCE`, such as
// `struct ucred` and `SO_PEERCRED`.
.target(
name: "CGRPCNIOTransportLinux",
dependencies: [],
cSettings: [
.define("_GNU_SOURCE")
]
),
Comment on lines +103 to +111

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, it turns out that we don't need to go as far as this: we can just add the C settings to the posix module, no need for a C module at all.


// Core module containing shared components for the NIOPosix and NIOTS variants.
.target(
name: "GRPCNIOTransportCore",
Expand Down Expand Up @@ -128,6 +138,7 @@ let targets: [Target] = [
name: "GRPCNIOTransportHTTP2Posix",
dependencies: [
.target(name: "GRPCNIOTransportCore"),
.target(name: "CGRPCNIOTransportLinux", condition: .when(platforms: [.linux])),
.product(name: "GRPCCore", package: "grpc-swift-2"),
.product(name: "NIOPosix", package: "swift-nio"),
.product(name: "NIOSSL", package: "swift-nio-ssl"),
Expand Down
19 changes: 19 additions & 0 deletions Sources/CGRPCNIOTransportLinux/empty.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2026, gRPC Authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// Xcode's Archive builds with Xcode's Package support struggle with empty .c files
// (https://bugs.swift.org/browse/SR-12939).
void CGRPCNIOTransportLinux_i_do_nothing_just_working_around_a_darwin_toolchain_bug(void) {}
32 changes: 32 additions & 0 deletions Sources/CGRPCNIOTransportLinux/include/CGRPCNIOTransportLinux.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2026, gRPC Authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef C_GRPC_NIO_TRANSPORT_LINUX_H_
#define C_GRPC_NIO_TRANSPORT_LINUX_H_

#ifdef __linux__

// glibc 2.36+ only declares `struct ucred` when `_GNU_SOURCE` is defined, and the Swift Glibc
// module does not reliably set it. This target defines `_GNU_SOURCE` (see Package.swift) so that
// including <sys/socket.h> here exposes `struct ucred` and `SO_PEERCRED` to Swift.
#ifndef _GNU_SOURCE
#error You must define _GNU_SOURCE
#endif

#include <sys/socket.h>

#endif // __linux__

#endif // C_GRPC_NIO_TRANSPORT_LINUX_H_
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,8 @@ extension HTTP2ServerTransport {
}
} catch {}

context.udsCredentials = await makeUDSCredentials(channel: channel)

return context
}
}
Expand Down Expand Up @@ -308,10 +310,81 @@ extension HTTP2ServerTransport.Posix {
@available(gRPCSwiftNIOTransport 2.2, *)
public var peerCertificateChain: X509.ValidatedCertificateChain?

/// Kernel-validated peer credentials of the connecting process when the
/// transport is bound to a Unix domain socket. `nil` for non-UDS
/// channels and on platforms or socket types where peer credentials are
/// unavailable.
@available(gRPCSwiftNIOTransport 2.10, *)
public var udsCredentials: UDSCredentials?

public init() {
}
}

/// Kernel-validated peer credentials reported by the operating system at
/// connect time for a Unix domain socket. They reflect the peer process as
/// recorded by the kernel when the connection was established; they are not
/// re-read per request.
@available(gRPCSwiftNIOTransport 2.10, *)
public struct UDSCredentials: Hashable, Sendable {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of these types are specific to server transport so shouldn't be nested within HTTP2ServerTransport. I think they can be moved to their own file in the core module.

/// The process ID of the peer process.
public var pid: ProcessID

/// The user ID of the peer process.
public var uid: UserID

/// The group ID of the peer process.
public var gid: GroupID
Comment on lines +331 to +337

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we spell these out rather than abbreviating them?


public init(pid: ProcessID, uid: UserID, gid: GroupID) {
self.pid = pid
self.uid = uid
self.gid = gid
}
}

/// The ID of a process.
@available(gRPCSwiftNIOTransport 2.10, *)
public struct ProcessID: Hashable, Sendable, RawRepresentable, ExpressibleByIntegerLiteral {
public var rawValue: Int32

public init(rawValue: Int32) {
self.rawValue = rawValue
}

public init(integerLiteral value: Int32) {
self.init(rawValue: value)
}
}

/// The ID of a user.
@available(gRPCSwiftNIOTransport 2.10, *)
public struct UserID: Hashable, Sendable, RawRepresentable, ExpressibleByIntegerLiteral {
public var rawValue: UInt32

public init(rawValue: UInt32) {
self.rawValue = rawValue
}

public init(integerLiteral value: UInt32) {
self.init(rawValue: value)
}
}

/// The ID of a group.
@available(gRPCSwiftNIOTransport 2.10, *)
public struct GroupID: Hashable, Sendable, RawRepresentable, ExpressibleByIntegerLiteral {
public var rawValue: UInt32

public init(rawValue: UInt32) {
self.rawValue = rawValue
}

public init(integerLiteral value: UInt32) {
self.init(rawValue: value)
}
}

/// Config for the `Posix` transport.
public struct Config: Sendable {
/// Compression configuration.
Expand Down
82 changes: 82 additions & 0 deletions Sources/GRPCNIOTransportHTTP2Posix/UDSPeerCredentials.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2026, gRPC Authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

internal import GRPCNIOTransportCore
internal import NIOCore

#if canImport(Darwin)
internal import Darwin
#elseif canImport(Glibc)
internal import Glibc
// glibc guards `struct ucred` and `SO_PEERCRED` behind `_GNU_SOURCE`, which the Glibc module
// doesn't reliably set, so they come from this shim instead.
internal import CGRPCNIOTransportLinux
#elseif canImport(Musl)
internal import Musl
internal import CGRPCNIOTransportLinux
#endif

/// Reads kernel-validated peer credentials from `channel`'s underlying socket.
///
/// Returns `nil` when the channel isn't backed by a Unix domain socket, when it isn't a
/// `SocketOptionProvider`, on platforms without peer-credential support, or on any read failure.
@available(gRPCSwiftNIOTransport 2.0, *)
internal func makeUDSCredentials(
channel: any Channel
) async -> HTTP2ServerTransport.Posix.UDSCredentials? {
// Peer credentials only exist for Unix domain sockets, so avoid the socket option reads
// altogether for any other transport.
guard case .some(.unixDomainSocket) = channel.localAddress else { return nil }
guard let provider = channel as? any SocketOptionProvider else { return nil }

#if canImport(Darwin)
do {
// Darwin reports the peer's effective PID and its credentials through two separate options.
let pid: pid_t = try await provider.unsafeGetSocketOption(
level: SocketOptionLevel(SOL_LOCAL),
name: SocketOptionName(LOCAL_PEEREPID)
).get()
let credentials: xucred = try await provider.unsafeGetSocketOption(
level: SocketOptionLevel(SOL_LOCAL),
name: SocketOptionName(LOCAL_PEERCRED)
).get()
// `cr_groups` is imported as a fixed-size tuple; the first element is the primary group.
return HTTP2ServerTransport.Posix.UDSCredentials(
pid: .init(rawValue: pid),
uid: .init(rawValue: credentials.cr_uid),
gid: .init(rawValue: credentials.cr_groups.0)
)
} catch {
return nil
}
#elseif canImport(Glibc) || canImport(Musl)
do {
let credentials: ucred = try await provider.unsafeGetSocketOption(
level: SocketOptionLevel(SOL_SOCKET),
name: SocketOptionName(SO_PEERCRED)
).get()
return HTTP2ServerTransport.Posix.UDSCredentials(
pid: .init(rawValue: credentials.pid),
uid: .init(rawValue: credentials.uid),
gid: .init(rawValue: credentials.gid)
)
} catch {
return nil
}
#else
return nil
#endif
}