Files
swift-nio/Sources/NIOCore/AddressedEnvelope.swift
Cory BenfieldandGitHub a7da98c3ac Enable per-message GSO on Linux (#3436)
Motivation:

NIO has existing GSO support. This GSO support is applied at Channel
scope, which makes it somewhat less than ideal, as it forces the
datagram sizes to be known statically ahead of time. In real
applications this is less common than being able to do this on a
per-superbuffer
context.

Linux supports this already by using sendmsg/sendmmsg control data. We
just need to wire this up in NIO.

An extension to this is that we can also do per-message GRO in Linux.
I'll tackle this in a later patch to keep the diffs small.

Modifications:

- Adding a new field on AddressedEnvelope Metadata.
- Reject setting this field on non-Linux devices.
- On Linux, use this to set the control data.
- Added new tests that validate the behaviour is correct.

Result:

Enable per-message GSO.
2025-11-07 08:34:33 +00:00

110 lines
3.9 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
/// A data structure for processing addressed datagrams, such as those used by UDP.
///
/// The AddressedEnvelope is used extensively on `DatagramChannel`s in order to keep track
/// of source or destination address metadata: that is, where some data came from or where
/// it is going.
public struct AddressedEnvelope<DataType> {
public var remoteAddress: SocketAddress
public var data: DataType
/// Any metadata associated with this `AddressedEnvelope`
public var metadata: Metadata? = nil
public init(remoteAddress: SocketAddress, data: DataType) {
self.remoteAddress = remoteAddress
self.data = data
}
public init(remoteAddress: SocketAddress, data: DataType, metadata: Metadata?) {
self.remoteAddress = remoteAddress
self.data = data
self.metadata = metadata
}
/// Any metadata associated with an `AddressedEnvelope`
public struct Metadata: Hashable, Sendable {
/// Details of any congestion state.
public var ecnState: NIOExplicitCongestionNotificationState
public var packetInfo: NIOPacketInfo?
/// The UDP segment size for Generic Segmentation Offload (GSO).
///
/// When set, this enables per-message GSO, allowing the kernel to split this datagram
/// into multiple segments of the specified size. The maximum segment size is platform-dependent
/// and can be queried via `System.udpMaxSegments`.
///
/// On non-Linux platforms, writes with a non-nil `segmentSize` will fail with
/// `ChannelError.operationUnsupported`. The error will be propagated to the write promise
/// (if attached).
public var segmentSize: Int?
public init(ecnState: NIOExplicitCongestionNotificationState) {
self.ecnState = ecnState
self.packetInfo = nil
self.segmentSize = nil
}
public init(ecnState: NIOExplicitCongestionNotificationState, packetInfo: NIOPacketInfo?) {
self.ecnState = ecnState
self.packetInfo = packetInfo
self.segmentSize = nil
}
public init(
ecnState: NIOExplicitCongestionNotificationState,
packetInfo: NIOPacketInfo?,
segmentSize: Int?
) {
self.ecnState = ecnState
self.packetInfo = packetInfo
self.segmentSize = segmentSize
}
}
}
extension AddressedEnvelope: CustomStringConvertible {
public var description: String {
"AddressedEnvelope { remoteAddress: \(self.remoteAddress), data: \(self.data) }"
}
}
extension AddressedEnvelope: Equatable where DataType: Equatable {}
extension AddressedEnvelope: Hashable where DataType: Hashable {}
extension AddressedEnvelope: Sendable where DataType: Sendable {}
/// Possible Explicit Congestion Notification States
public enum NIOExplicitCongestionNotificationState: Hashable, Sendable {
/// Non-ECN Capable Transport.
case transportNotCapable
/// ECN Capable Transport (flag 0).
case transportCapableFlag0
/// ECN Capable Transport (flag 1).
case transportCapableFlag1
/// Congestion Experienced.
case congestionExperienced
}
public struct NIOPacketInfo: Hashable, Sendable {
public var destinationAddress: SocketAddress
public var interfaceIndex: Int
public init(destinationAddress: SocketAddress, interfaceIndex: Int) {
self.destinationAddress = destinationAddress
self.interfaceIndex = interfaceIndex
}
}