-
Notifications
You must be signed in to change notification settings - Fork 44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for UDP #129
Add support for UDP #129
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #129 +/- ##
==========================================
- Coverage 96.66% 89.62% -7.04%
==========================================
Files 60 60
Lines 3504 3932 +428
==========================================
+ Hits 3387 3524 +137
- Misses 117 408 +291 ☔ View full report in Codecov by Sentry. |
You mean I have to write tests? ;) |
This is really great thank you. I think the changes to I don't think we necessarily need I've have built a little echo server using your code with a couple of small tweaks: ![]() From here it would be easy to add a No need to worry about tests until we settle on the right API |
Great, have applied your diff locally, will push an update shortly. |
I do need |
fbe7c44
to
9c7be02
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is wonderful, thank you
Because Swift Testing runs tests in parallel you may need to bind to port 0 letting the OS provide a port number then retrieve it and send to it let address: any SocketAddress = .loopback(port: 0)
let socket = try Socket(domain: Int32(type(of: address).family), type: Int32(SOCK_DGRAM))
try socket.bind(to: address)
let listenAddr = try socket.sockname() // gets port |
Great, I ran out of time today but I'll take a look tomorrow! |
BTW, UDP sockets can be connected and there can be some benefits to use this on the client side (it can save a route lookup as it can be cached in the "connection"). I'll add support for that too. Edit: actually we can probably just use read and write, they should be identical to recv and send with no flags (shall triple check). |
We might want to support multihomed servers without needing to bind separate sockets to each interface. |
So for this I reckon maybe we want to make the message a structure rather than a tuple (as I had it originally, although I'm not wedded to this), and then add the socket address on which the packet was received and the interface index. These should be usable on both send and receive and be optional (or possibly omitted entirely from the structure if the underlying platform doesn't support). Something like: struct /*AsyncSocket.*/Message: Sendable {
let data: [UInt8]
let peerAddress: sockaddr_storage
let localAddress: sockaddr_storage?
let interfaceIndex: Int32?
} Dealing with |
that's fine, its an additional feature so can skip a platform without breaking anything |
~~For Windows, On the other hand, use cases for a multihomed UDP server on Windows... I mean, it seems unlikely to me but, I'm sure someone can think of one.~~ Scratch that, Windows API is now consistent, it just doesn't return the local interface index and address. public struct AsyncSocketMessageSequence: AsyncSequence, AsyncIteratorProtocol, Sendable {
public static let DefaultMaxMessageLength: Int = 1500
// Windows has a different recvmsg() API signature which is presently unsupported
public typealias Element = AsyncSocket.Message
private let socket: AsyncSocket
private let maxMessageLength: Int
public func makeAsyncIterator() -> AsyncSocketMessageSequence { self }
init(socket: AsyncSocket, maxMessageLength: Int = Self.DefaultMaxMessageLength) {
self.socket = socket
self.maxMessageLength = maxMessageLength
}
public mutating func next() async throws -> Element? {
#if !canImport(WinSDK)
try await socket.receive(atMost: maxMessageLength)
#else
let peerAddress: sockaddr_storage
let bytes: [UInt8]
(peerAddress, bytes) = try await socket.receive(atMost: maxMessageLength)
return AsyncSocket.Message(peerAddress: peerAddress, bytes: bytes)
#endif
}
} |
b384839
to
6fb4b23
Compare
Insulate callers from libc types by adding a `SocketType` enumerated type, that can either be `stream` or `datagram`.
187104e
to
9ce84ec
Compare
I've made a couple of other changes which you may/may not wish to take. I was a bit annoyed by needing to use And also there's an |
27c008b
to
1705f87
Compare
Add support for datagram sockets, wrapping sendto() and recvfrom(). Note: send() and recv() are not supported, so a destination address must be supplied; however write() and read() can be used with datagram sockets. Fixes: swhitty#128
Multihomed servers (i.e. those with multiple network interfaces) need to take care to send UDP responses from the interface on which they were received, otherwise the client may never receive the packet. Historically this was done by binding separate sockets to each interface, however this does not adapt well to dynamic interface changes (without extra code to monitor for this, which is impossible to do in a portable manner). Modern operating systems provide `IP_PKTINFO` and `IPV6_PKTINFO` which allow the local interface index and address to be set and reported on unbound sockets. This commit adds support for this. Note: currently unavailable on Windows.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work
Thanks Simon, really helps out my project too having this merged! |
I was adding some more unit tests and discovered an issue with IP6 on both Darwin and Linux: try Socket(domain: AF_INET6, type: .datagram) This always throws an error: While I have not fixed the issue, I have a PR #131 that I would appreciate your input on, because it replaces the |
No description provided.