From 507f6b39e0997a9acecd6f207e99b905a81b9321 Mon Sep 17 00:00:00 2001 From: George Barnett Date: Tue, 31 May 2022 15:17:02 +0100 Subject: [PATCH 1/4] Update PCAP example to be async Motivation: Our examples should at least attempt to be up-to-date. The PCAP example currenly uses the NIO based API, we should switch it to the async API. Modifications: - Update the PCAP example to use async/await - Ditch logging for print Result: PCAP example is async/await --- Package.swift | 1 - Sources/Examples/PacketCapture/Empty.swift | 17 ++ .../PacketCapture/PacketCapture.swift | 87 +++++++++++ Sources/Examples/PacketCapture/README.md | 12 +- Sources/Examples/PacketCapture/main.swift | 146 ------------------ 5 files changed, 105 insertions(+), 158 deletions(-) create mode 100644 Sources/Examples/PacketCapture/Empty.swift create mode 100644 Sources/Examples/PacketCapture/PacketCapture.swift delete mode 100644 Sources/Examples/PacketCapture/main.swift diff --git a/Package.swift b/Package.swift index 0d00deea2..149dd27a5 100644 --- a/Package.swift +++ b/Package.swift @@ -409,7 +409,6 @@ extension Target { .nioCore, .nioPosix, .nioExtras, - .logging, .argumentParser, ], path: "Sources/Examples/PacketCapture", diff --git a/Sources/Examples/PacketCapture/Empty.swift b/Sources/Examples/PacketCapture/Empty.swift new file mode 100644 index 000000000..f8c631871 --- /dev/null +++ b/Sources/Examples/PacketCapture/Empty.swift @@ -0,0 +1,17 @@ +/* + * Copyright 2022, 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. + */ + +// This file exists to workaround https://github.com/apple/swift/issues/55127. diff --git a/Sources/Examples/PacketCapture/PacketCapture.swift b/Sources/Examples/PacketCapture/PacketCapture.swift new file mode 100644 index 000000000..86cfffac8 --- /dev/null +++ b/Sources/Examples/PacketCapture/PacketCapture.swift @@ -0,0 +1,87 @@ +/* + * Copyright 2020, 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. + */ +#if compiler(>=5.6) +import ArgumentParser +import EchoModel +import GRPC +import NIOCore +import NIOExtras +import NIOPosix + +@main +@available(macOS 10.15, *) +struct PCAP: AsyncParsableCommand { + @Option(help: "The port to connect to") + var port = 1234 + + func run() async throws { + // Create an `EventLoopGroup`. + let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) + defer { + try! group.syncShutdownGracefully() + } + + // The filename for the .pcap file to write to. + let path = "packet-capture-example.pcap" + let fileSink = try NIOWritePCAPHandler.SynchronizedFileSink.fileSinkWritingToFile( + path: path + ) { error in + print("Failed to write with error '\(error)' for path '\(path)'") + } + + // Ensure that we close the file sink when we're done with it. + defer { + try? fileSink.syncClose() + } + + let channel = try GRPCChannelPool.with( + target: .host("localhost", port: self.port), + transportSecurity: .plaintext, + eventLoopGroup: group + ) { + $0.debugChannelInitializer = { channel in + // Create the PCAP handler and add it to the start of the channel pipeline. If this example + // used TLS we would likely want to place the handler in a different position in the + // pipeline so that the captured packets in the trace would not be encrypted. + let writePCAPHandler = NIOWritePCAPHandler(mode: .client, fileSink: fileSink.write(buffer:)) + return channel.eventLoop.makeCompletedFuture(Result { + try channel.pipeline.syncOperations.addHandler(writePCAPHandler, position: .first) + }) + } + } + + // Create a client. + let echo = Echo_EchoAsyncClient(channel: channel) + + let messages = ["foo", "bar", "baz", "thud", "grunt", "gorp"].map { text in + Echo_EchoRequest.with { $0.text = text } + } + + do { + for try await response in echo.update(messages) { + print("Received response '\(response.text)'") + } + print("RPC completed successfully") + } catch { + print("RPC failed with error '\(error)'") + } + + print("Try opening '\(path)' in Wireshark or with 'tcpdump -r \(path)'") + + try await echo.channel.close().get() + } +} +#endif // compiler(>=5.6) diff --git a/Sources/Examples/PacketCapture/README.md b/Sources/Examples/PacketCapture/README.md index 3b490229a..bd8ad3313 100644 --- a/Sources/Examples/PacketCapture/README.md +++ b/Sources/Examples/PacketCapture/README.md @@ -26,17 +26,7 @@ In a separate shell run: $ swift run PacketCapture ``` -Some logs should be emitted similar to below, including the path of the -*.pcap* file: - -```sh -2020-07-24T10:48:50+0100 info gRPC PCAP Demo : Creating fileSink for path './channel-ObjectIdentifier(0x00007f8a25604c40).pcap' -2020-07-24T10:48:50+0100 info gRPC PCAP Demo : ✅ Successfully created fileSink for path './channel-ObjectIdentifier(0x00007f8a25604c40).pcap' -... -2020-07-24T10:48:50+0100 info gRPC PCAP Demo : ✅ RPC completed successfully -... -2020-07-24T10:48:50+0100 info gRPC PCAP Demo : Done! -``` +The pcap file will be written to 'packet-capture-example.pcap'. The *.pcap* file can be opened with either: [Wireshark][wireshark] or `tcpdump -r `. diff --git a/Sources/Examples/PacketCapture/main.swift b/Sources/Examples/PacketCapture/main.swift deleted file mode 100644 index 15fcc3bc8..000000000 --- a/Sources/Examples/PacketCapture/main.swift +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2020, 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. - */ -#if compiler(>=5.6) -import ArgumentParser -import Dispatch -import EchoModel -import GRPC -import Logging -import NIOCore -import NIOExtras -import NIOPosix - -// Create a logger. -let logger = Logger(label: "gRPC PCAP Demo") - -// Closing file sinks is blocking, it therefore can't be done on an EventLoop. -let fileSinkCloseQueue = DispatchQueue(label: "io.grpc") -let fileSinkCloseGroup = DispatchGroup() -defer { - // Make sure we wait for all file sinks to be closed before we exit. - fileSinkCloseGroup.wait() - logger.info("Done!") -} - -/// Adds a `NIOWritePCAPHandler` to the given channel. -/// -/// A file sink will also be created to write the PCAP to `./channel-{ID}.pcap` where `{ID}` is -/// an identifier created from the given `channel`. The file sink will be closed when the channel -/// closes and will notify the `fileSinkCloseGroup` when it has been closed. -/// -/// - Parameter channel: The channel to add the PCAP handler to. -/// - Returns: An `EventLoopFuture` indicating whether the PCAP handler was successfully added. -@Sendable -func addPCAPHandler(toChannel channel: Channel) -> EventLoopFuture { - // The debug initializer can be called multiple times. We'll use the object ID of the channel - // to disambiguate between the files. - let channelID = ObjectIdentifier(channel) - let path = "./channel-\(channelID).pcap" - - logger.info("Creating fileSink for path '\(path)'") - - do { - // Create a file sink. - let fileSink = try NIOWritePCAPHandler.SynchronizedFileSink - .fileSinkWritingToFile(path: path) { error in - logger.error("💥 Failed to write with error '\(error)' for path '\(path)'") - } - - logger.info("✅ Successfully created fileSink for path '\(path)'") - - // We need to close the file sink when we're done. It can't be closed from the event loop so - // we'll use a dispatch queue instead. - fileSinkCloseGroup.enter() - channel.closeFuture.whenComplete { _ in - fileSinkCloseQueue.async { - do { - try fileSink.syncClose() - } catch { - logger.error("💥 Failed to close fileSink with error '\(error)' for path '\(path)'") - } - } - fileSinkCloseGroup.leave() - } - - // Add the handler to the pipeline. - let handler = NIOWritePCAPHandler(mode: .client, fileSink: fileSink.write(buffer:)) - // We're not using TLS in this example so ".first" is the right place. - return channel.pipeline.addHandler(handler, position: .first) - } catch { - logger.error("💥 Failed to create fileSink with error '\(error)' for path '\(path)'") - return channel.eventLoop.makeFailedFuture(error) - } -} - -struct PCAP: ParsableCommand { - @Option(help: "The port to connect to") - var port = 1234 - - func run() throws { - // Create an `EventLoopGroup`. - let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - try! group.syncShutdownGracefully() - } - - // Create a channel. - let channel = ClientConnection.insecure(group: group) - // Set the debug initializer: it will add a handler to each created channel to write a PCAP when - // the channel is closed. - .withDebugChannelInitializer(addPCAPHandler(toChannel:)) - // We're connecting to our own server here; we'll disable connection re-establishment. - .withConnectionReestablishment(enabled: false) - // Connect! - .connect(host: "localhost", port: self.port) - - // Create a client. - let echo = Echo_EchoNIOClient(channel: channel) - - // Start an RPC. - let update = echo.update { response in - logger.info("Received response '\(response.text)'") - } - - // Send some requests. - for text in ["foo", "bar", "baz", "thud", "grunt", "gorp"] { - update.sendMessage(.with { $0.text = text }).whenSuccess { - logger.info("Sent request '\(text)'") - } - } - - // Close the request stream. - update.sendEnd(promise: nil) - - // Once the RPC finishes close the connection. - let closed = update.status.flatMap { status -> EventLoopFuture in - if status.isOk { - logger.info("✅ RPC completed successfully") - } else { - logger.error("💥 RPC failed with status '\(status)'") - } - logger.info("Closing channel") - return channel.close() - } - - // Wait for the channel to be closed. - try closed.wait() - } -} - -PCAP.main() -#else -fatalError("This example requires Swift 5.6.") -#endif // compiler(>=5.6) From b145e0531923c3ce029f0e34aefe02c1c18534e0 Mon Sep 17 00:00:00 2001 From: George Barnett Date: Wed, 1 Jun 2022 15:04:25 +0100 Subject: [PATCH 2/4] Fix main for older versions --- Sources/Examples/PacketCapture/PacketCapture.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/Examples/PacketCapture/PacketCapture.swift b/Sources/Examples/PacketCapture/PacketCapture.swift index 86cfffac8..0fc0ab76b 100644 --- a/Sources/Examples/PacketCapture/PacketCapture.swift +++ b/Sources/Examples/PacketCapture/PacketCapture.swift @@ -84,4 +84,11 @@ struct PCAP: AsyncParsableCommand { try await echo.channel.close().get() } } +#else +@main +struct PCAP { + static func main() { + print("This example requires Swift >= 5.6") + } +} #endif // compiler(>=5.6) From 3b4623ba3bae10d9cf6163b0da063ba591640e37 Mon Sep 17 00:00:00 2001 From: George Barnett Date: Wed, 1 Jun 2022 15:05:44 +0100 Subject: [PATCH 3/4] Update Sources/Examples/PacketCapture/PacketCapture.swift --- Sources/Examples/PacketCapture/PacketCapture.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Examples/PacketCapture/PacketCapture.swift b/Sources/Examples/PacketCapture/PacketCapture.swift index 0fc0ab76b..e30796171 100644 --- a/Sources/Examples/PacketCapture/PacketCapture.swift +++ b/Sources/Examples/PacketCapture/PacketCapture.swift @@ -44,7 +44,7 @@ struct PCAP: AsyncParsableCommand { // Ensure that we close the file sink when we're done with it. defer { - try? fileSink.syncClose() + try! fileSink.syncClose() } let channel = try GRPCChannelPool.with( From b52d0cbd699c2ab1e953f79d07aed719abc9204c Mon Sep 17 00:00:00 2001 From: George Barnett Date: Wed, 1 Jun 2022 15:36:33 +0100 Subject: [PATCH 4/4] formatting --- Sources/Examples/PacketCapture/PacketCapture.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Examples/PacketCapture/PacketCapture.swift b/Sources/Examples/PacketCapture/PacketCapture.swift index e30796171..15178576e 100644 --- a/Sources/Examples/PacketCapture/PacketCapture.swift +++ b/Sources/Examples/PacketCapture/PacketCapture.swift @@ -86,7 +86,7 @@ struct PCAP: AsyncParsableCommand { } #else @main -struct PCAP { +enum PCAP { static func main() { print("This example requires Swift >= 5.6") }