chore: import upstream snapshot with attribution
container project - merge build / Invoke build (push) Successful in 1s

This commit is contained in:
wehub-resource-sync
2026-07-13 12:06:18 +08:00
commit e84fb4a79e
474 changed files with 68321 additions and 0 deletions
@@ -0,0 +1,114 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the container project authors.
//
// 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
//
// https://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.
//===----------------------------------------------------------------------===//
import Logging
import NIOCore
import NIOPosix
final class ConnectHandler {
private var pendingBytes: [NIOAny]
private let serverAddress: SocketAddress
private var log: Logger? = nil
init(serverAddress: SocketAddress, log: Logger?) {
self.pendingBytes = []
self.serverAddress = serverAddress
self.log = log
}
}
extension ConnectHandler: ChannelInboundHandler {
typealias InboundIn = ByteBuffer
typealias OutboundOut = ByteBuffer
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
self.pendingBytes.append(data)
}
func handlerAdded(context: ChannelHandlerContext) {
// Add logger metadata.
self.log?[metadataKey: "proxy"] = "\(context.channel.localAddress?.description ?? "none")"
self.log?[metadataKey: "server"] = "\(context.channel.remoteAddress?.description ?? "none")"
}
func channelActive(context: ChannelHandlerContext) {
self.log?.trace("frontend - channel active, connecting to backend")
self.connectToServer(context: context)
context.fireChannelActive()
}
}
extension ConnectHandler: RemovableChannelHandler {
func removeHandler(context: ChannelHandlerContext, removalToken: ChannelHandlerContext.RemovalToken) {
var didRead = false
// We are being removed, and need to deliver any pending bytes we may have if we're upgrading.
while self.pendingBytes.count > 0 {
let data = self.pendingBytes.removeFirst()
context.fireChannelRead(data)
didRead = true
}
if didRead {
context.fireChannelReadComplete()
}
self.log?.trace("backend - removing connect handler from pipeline")
context.leavePipeline(removalToken: removalToken)
}
}
extension ConnectHandler {
private func connectToServer(context: ChannelHandlerContext) {
self.log?.trace("backend - connecting")
ClientBootstrap(group: context.eventLoop)
.connect(to: serverAddress)
.assumeIsolatedUnsafeUnchecked()
.whenComplete { result in
switch result {
case .success(let channel):
guard context.channel.isActive else {
self.log?.trace("backend - frontend channel closed, closing backend connection")
context.channel.close(promise: nil)
return
}
self.log?.trace("backend - connected")
self.glue(channel, context: context)
case .failure(let error):
self.log?.error("backend - connect failed: \(error)")
context.close(promise: nil)
context.fireErrorCaught(error)
}
}
}
private func glue(_ peerChannel: Channel, context: ChannelHandlerContext) {
self.log?.trace("backend - gluing channels")
// Now we need to glue our channel and the peer channel together.
let (localGlue, peerGlue) = GlueHandler.matchedPair()
do {
try context.channel.pipeline.syncOperations.addHandler(localGlue)
try peerChannel.pipeline.syncOperations.addHandler(peerGlue)
context.pipeline.syncOperations.removeHandler(self, promise: nil)
} catch {
// Close connected peer channel before closing our channel.
peerChannel.close(mode: .all, promise: nil)
context.close(promise: nil)
}
}
}
+121
View File
@@ -0,0 +1,121 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the container project authors.
//
// 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
//
// https://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.
//===----------------------------------------------------------------------===//
import NIOCore
final class GlueHandler {
private var partner: GlueHandler?
private var context: ChannelHandlerContext?
private var pendingRead: Bool = false
private init() {}
}
extension GlueHandler {
static func matchedPair() -> (GlueHandler, GlueHandler) {
let first = GlueHandler()
let second = GlueHandler()
first.partner = second
second.partner = first
return (first, second)
}
}
extension GlueHandler {
private func partnerWrite(_ data: NIOAny) {
self.context?.write(data, promise: nil)
}
private func partnerFlush() {
self.context?.flush()
}
private func partnerWriteEOF() {
self.context?.close(mode: .output, promise: nil)
}
private func partnerCloseFull() {
self.context?.close(promise: nil)
}
private func partnerBecameWritable() {
if self.pendingRead {
self.pendingRead = false
self.context?.read()
}
}
private var partnerWritable: Bool {
self.context?.channel.isWritable ?? false
}
}
extension GlueHandler: ChannelDuplexHandler {
typealias InboundIn = NIOAny
typealias OutboundIn = NIOAny
typealias OutboundOut = NIOAny
func handlerAdded(context: ChannelHandlerContext) {
self.context = context
}
func handlerRemoved(context: ChannelHandlerContext) {
self.context = nil
self.partner = nil
}
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
self.partner?.partnerWrite(data)
}
func channelReadComplete(context: ChannelHandlerContext) {
self.partner?.partnerFlush()
}
func channelInactive(context: ChannelHandlerContext) {
self.partner?.partnerCloseFull()
}
func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
if let event = event as? ChannelEvent, case .inputClosed = event {
// We have read EOF.
self.partner?.partnerWriteEOF()
}
}
func errorCaught(context: ChannelHandlerContext, error: Error) {
self.partner?.partnerCloseFull()
}
func channelWritabilityChanged(context: ChannelHandlerContext) {
if context.channel.isWritable {
self.partner?.partnerBecameWritable()
}
}
func read(context: ChannelHandlerContext) {
if let partner = self.partner, partner.partnerWritable {
context.read()
} else {
self.pendingRead = true
}
}
}
+118
View File
@@ -0,0 +1,118 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the container project authors.
//
// 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
//
// https://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.
//===----------------------------------------------------------------------===//
struct KeyExistsError: Error {}
class LRUCache<K: Hashable, V> {
private class Node {
fileprivate var prev: Node?
fileprivate var next: Node?
fileprivate let key: K
fileprivate let value: V
init(key: K, value: V) {
self.prev = nil
self.next = nil
self.key = key
self.value = value
}
}
private let size: UInt
private var head: Node?
private var tail: Node?
private var members: [K: Node]
init(size: UInt) {
self.size = size
self.head = nil
self.tail = nil
self.members = [:]
}
var count: Int { members.count }
func get(_ key: K) -> V? {
guard let node = members[key] else {
return nil
}
listRemove(node: node)
listInsert(node: node, after: tail)
return node.value
}
func put(key: K, value: V) -> (K, V)? {
let node = Node(key: key, value: value)
var evicted: (K, V)? = nil
if let existingNode = members[key] {
// evict the replaced node
listRemove(node: existingNode)
evicted = (existingNode.key, existingNode.value)
} else if self.count >= self.size {
// evict the least recently used node
evicted = evict()
}
// insert the new node and return any evicted node
members[key] = node
listInsert(node: node, after: tail)
return evicted
}
private func evict() -> (K, V)? {
guard let head else {
return nil
}
let ret = (head.key, head.value)
listRemove(node: head)
members.removeValue(forKey: head.key)
return ret
}
private func listRemove(node: Node) {
if let prev = node.prev {
prev.next = node.next
} else {
head = node.next
}
if let next = node.next {
next.prev = node.prev
} else {
tail = node.prev
}
}
private func listInsert(node: Node, after: Node?) {
let before: Node?
if let after {
before = after.next
after.next = node
} else {
before = head
head = node
}
if let before {
before.prev = node
} else {
tail = node
}
node.prev = after
node.next = before
}
}
@@ -0,0 +1,21 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the container project authors.
//
// 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
//
// https://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.
//===----------------------------------------------------------------------===//
import NIO
public protocol SocketForwarder: Sendable {
func run() throws -> EventLoopFuture<SocketForwarderResult>
}
@@ -0,0 +1,37 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the container project authors.
//
// 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
//
// https://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.
//===----------------------------------------------------------------------===//
import NIO
public struct SocketForwarderResult: Sendable {
private let channel: any Channel
public init(channel: Channel) {
self.channel = channel
}
public var proxyAddress: SocketAddress? { self.channel.localAddress }
public func close() {
self.channel.eventLoop.execute {
_ = channel.close()
}
}
public func wait() async throws {
try await self.channel.closeFuture.get()
}
}
@@ -0,0 +1,62 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the container project authors.
//
// 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
//
// https://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.
//===----------------------------------------------------------------------===//
import Foundation
import Logging
import NIO
import NIOFoundationCompat
public struct TCPForwarder: SocketForwarder {
private let proxyAddress: SocketAddress
private let serverAddress: SocketAddress
private let eventLoopGroup: any EventLoopGroup
private let log: Logger?
public init(
proxyAddress: SocketAddress,
serverAddress: SocketAddress,
eventLoopGroup: any EventLoopGroup,
log: Logger? = nil
) throws {
self.proxyAddress = proxyAddress
self.serverAddress = serverAddress
self.eventLoopGroup = eventLoopGroup
self.log = log
}
public func run() throws -> EventLoopFuture<SocketForwarderResult> {
self.log?.trace("frontend - creating listener")
let bootstrap = ServerBootstrap(group: self.eventLoopGroup)
.serverChannelOption(ChannelOptions.socket(.init(SOL_SOCKET), .init(SO_REUSEADDR)), value: 1)
.childChannelOption(ChannelOptions.socket(.init(SOL_SOCKET), .init(SO_REUSEADDR)), value: 1)
.childChannelInitializer { channel in
channel.eventLoop.makeCompletedFuture {
try channel.pipeline.syncOperations.addHandler(
ConnectHandler(serverAddress: self.serverAddress, log: log)
)
}
}
return
bootstrap
.bind(to: self.proxyAddress)
.map { SocketForwarderResult(channel: $0) }
}
}
+205
View File
@@ -0,0 +1,205 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the container project authors.
//
// 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
//
// https://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.
//===----------------------------------------------------------------------===//
import Collections
import Foundation
import Logging
import NIO
import NIOFoundationCompat
import Synchronization
// Proxy backend for a single client address (clientIP, clientPort).
private final class UDPProxyBackend: ChannelInboundHandler {
typealias InboundIn = AddressedEnvelope<ByteBuffer>
typealias OutboundOut = AddressedEnvelope<ByteBuffer>
private struct State {
var queuedPayloads: Deque<ByteBuffer>
var channel: (any Channel)?
}
private let clientAddress: SocketAddress
private let serverAddress: SocketAddress
private let frontendChannel: any Channel
private let log: Logger?
private var state: State
init(clientAddress: SocketAddress, serverAddress: SocketAddress, frontendChannel: any Channel, log: Logger? = nil) {
self.clientAddress = clientAddress
self.serverAddress = serverAddress
self.frontendChannel = frontendChannel
self.log = log
let initialState = State(queuedPayloads: Deque(), channel: nil)
self.state = initialState
}
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
// relay data from server to client.
let inbound = self.unwrapInboundIn(data)
let outbound = OutboundOut(remoteAddress: self.clientAddress, data: inbound.data)
self.log?.trace("backend - writing datagram to client")
self.frontendChannel.writeAndFlush(outbound, promise: nil)
}
func channelActive(context: ChannelHandlerContext) {
if !state.queuedPayloads.isEmpty {
self.log?.trace("backend - writing \(state.queuedPayloads.count) queued datagrams to server")
while let queuedData = state.queuedPayloads.popFirst() {
let outbound: UDPProxyBackend.OutboundOut = OutboundOut(remoteAddress: self.serverAddress, data: queuedData)
context.channel.writeAndFlush(outbound, promise: nil)
}
}
state.channel = context.channel
}
func write(data: ByteBuffer) {
// change package remote address from proxy server to real server
if let channel = state.channel {
// channel has been initialized, so relay any queued packets, along with this one to outbound
self.log?.trace("backend - writing datagram to server")
let outbound: UDPProxyBackend.OutboundOut = OutboundOut(remoteAddress: self.serverAddress, data: data)
channel.writeAndFlush(outbound, promise: nil)
} else {
// channel is initializing, queue
self.log?.trace("backend - queuing datagram")
state.queuedPayloads.append(data)
}
}
func close() {
guard let channel = state.channel else {
self.log?.warning("backend - close on inactive channel")
return
}
_ = channel.close()
}
}
private struct ProxyContext {
public let proxy: UDPProxyBackend
public let closeFuture: EventLoopFuture<Void>
}
private final class UDPProxyFrontend: ChannelInboundHandler {
typealias InboundIn = AddressedEnvelope<ByteBuffer>
typealias OutboundOut = AddressedEnvelope<ByteBuffer>
private let maxProxies = UInt(256)
private let proxyAddress: SocketAddress
private let serverAddress: SocketAddress
private let log: Logger?
private var proxies: LRUCache<String, ProxyContext>
init(proxyAddress: SocketAddress, serverAddress: SocketAddress, log: Logger? = nil) {
self.proxyAddress = proxyAddress
self.serverAddress = serverAddress
self.proxies = LRUCache(size: maxProxies)
self.log = log
}
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
let inbound = self.unwrapInboundIn(data)
guard let clientIP = inbound.remoteAddress.ipAddress else {
log?.error("frontend - no client IP address in inbound payload")
return
}
guard let clientPort = inbound.remoteAddress.port else {
log?.error("frontend - no client port in inbound payload")
return
}
let key = "\(clientIP):\(clientPort)"
do {
if let context = proxies.get(key) {
context.proxy.write(data: inbound.data)
} else {
self.log?.trace("frontend - creating backend")
let proxy = UDPProxyBackend(
clientAddress: inbound.remoteAddress,
serverAddress: self.serverAddress,
frontendChannel: context.channel,
log: log
)
let proxyAddress = try SocketAddress(ipAddress: "0.0.0.0", port: 0)
let loopBoundProxy = NIOLoopBound(proxy, eventLoop: context.eventLoop)
let proxyToServerFuture = DatagramBootstrap(group: context.eventLoop)
.channelInitializer { [log] channel in
log?.trace("frontend - initializing backend")
return channel.eventLoop.makeCompletedFuture {
try channel.pipeline.syncOperations.addHandler(loopBoundProxy.value)
}
}
.bind(to: proxyAddress)
.flatMap { $0.closeFuture }
let context = ProxyContext(proxy: proxy, closeFuture: proxyToServerFuture)
if let (_, evictedContext) = proxies.put(key: key, value: context) {
self.log?.trace("frontend - closing evicted backend")
evictedContext.proxy.close()
}
proxy.write(data: inbound.data)
}
} catch {
log?.error("server handler - backend channel creation failed with error: \(error)")
return
}
}
}
public struct UDPForwarder: SocketForwarder {
private let proxyAddress: SocketAddress
private let serverAddress: SocketAddress
private let eventLoopGroup: any EventLoopGroup
private let log: Logger?
public init(
proxyAddress: SocketAddress,
serverAddress: SocketAddress,
eventLoopGroup: any EventLoopGroup,
log: Logger? = nil
) throws {
self.proxyAddress = proxyAddress
self.serverAddress = serverAddress
self.eventLoopGroup = eventLoopGroup
self.log = log
}
public func run() throws -> EventLoopFuture<SocketForwarderResult> {
self.log?.trace("frontend - creating channel")
let bootstrap = DatagramBootstrap(group: self.eventLoopGroup)
.channelInitializer { serverChannel in
self.log?.trace("frontend - initializing channel")
let proxyToServerHandler = UDPProxyFrontend(
proxyAddress: proxyAddress,
serverAddress: serverAddress,
log: log
)
return serverChannel.eventLoop.makeCompletedFuture {
try serverChannel.pipeline.syncOperations.addHandler(proxyToServerHandler)
}
}
return
bootstrap
.bind(to: proxyAddress)
.map { SocketForwarderResult(channel: $0) }
}
}