-
Notifications
You must be signed in to change notification settings - Fork 24
Make DNSSD queries cancellable #34
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
base: main
Are you sure you want to change the base?
Changes from 5 commits
610463d
a6299e5
de2fd16
8be4b7d
472d5c0
6ad9bd9
8102fb4
792d8e1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -110,20 +110,7 @@ struct DNSSD { | |
let recordStream = AsyncThrowingStream<ReplyHandler.Record, Error> { continuation in | ||
let handler = QueryReplyHandler(handler: replyHandler, continuation) | ||
|
||
// Wrap `handler` into a pointer so we can pass it to DNSServiceQueryRecord | ||
let handlerPointer = UnsafeMutableRawPointer.allocate( | ||
byteCount: MemoryLayout<QueryReplyHandler>.stride, | ||
alignment: MemoryLayout<QueryReplyHandler>.alignment | ||
) | ||
|
||
handlerPointer.initializeMemory(as: QueryReplyHandler.self, repeating: handler, count: 1) | ||
|
||
// The handler might be called multiple times so don't deallocate inside `callback` | ||
defer { | ||
let pointer = handlerPointer.assumingMemoryBound(to: QueryReplyHandler.self) | ||
pointer.deinitialize(count: 1) | ||
pointer.deallocate() | ||
} | ||
let query = Query(handler: handler) | ||
|
||
// This is called once per record received | ||
let callback: DNSServiceQueryRecordReply = { _, _, _, errorCode, _, _, _, rdlen, rdata, _, context in | ||
|
@@ -138,32 +125,42 @@ struct DNSSD { | |
handler.handleRecord(errorCode: errorCode, data: rdata, length: rdlen) | ||
} | ||
|
||
let serviceRefPtr = UnsafeMutablePointer<DNSServiceRef?>.allocate(capacity: 1) | ||
defer { serviceRefPtr.deallocate() } | ||
|
||
// Run the query | ||
let _code = DNSServiceQueryRecord( | ||
serviceRefPtr, | ||
query.serviceRefPtr, | ||
kDNSServiceFlagsTimeout, | ||
0, | ||
name, | ||
UInt16(type.kDNSServiceType), | ||
UInt16(kDNSServiceClass_IN), | ||
callback, | ||
handlerPointer | ||
query.replyHandlerPointer | ||
) | ||
|
||
// Check if query completed successfully | ||
guard _code == kDNSServiceErr_NoError else { | ||
return continuation.finish(throwing: AsyncDNSResolver.Error(dnssdCode: _code)) | ||
} | ||
|
||
// Read reply from the socket (blocking) then call reply handler | ||
DNSServiceProcessResult(serviceRefPtr.pointee) | ||
DNSServiceRefDeallocate(serviceRefPtr.pointee) | ||
let serviceSockFD = DNSServiceRefSockFD(query.serviceRefPtr.pointee) | ||
guard serviceSockFD != -1 else { | ||
return continuation.finish(throwing: AsyncDNSResolver.Error(code: .internalError, message: "Failed to access the DNSSD service socket")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aren't we leaking the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed the code so that the deallocates are also called in this branch and the other error branch just above. |
||
} | ||
|
||
let readSource = DispatchSource.makeReadSource(fileDescriptor: serviceSockFD) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we be passing our own queue here rather than using the default? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isn't this file descriptor leaked? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I close it now in the cancellation handler There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you'll need DispatchSources that use file descriptors and don't use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, thanks for the feedback! I implemented it in the latest commit |
||
readSource.setEventHandler { | ||
// Read reply from the socket (blocking) then call reply handler | ||
DNSServiceProcessResult(query.serviceRefPtr.pointee) | ||
|
||
// Streaming done | ||
continuation.finish() | ||
// Streaming done | ||
continuation.finish() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should cancel the read source There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Applied in latest commit |
||
} | ||
readSource.resume() | ||
|
||
continuation.onTermination = { _ in | ||
readSource.cancel() | ||
DNSServiceRefDeallocate(query.serviceRefPtr.pointee) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. security relevant use after free bug here, you could be concurrently deallocating this as well as using it in the event handler There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The deallocation is now done in the dispatch source cancel handler so that should no longer happen |
||
} | ||
} | ||
|
||
// Build reply using records received | ||
|
@@ -179,6 +176,27 @@ struct DNSSD { | |
|
||
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) | ||
extension DNSSD { | ||
// Class used to manage both the handler and the serviceRef pointers, so that they are deallocated when we are done using them | ||
class Query { | ||
let serviceRefPtr = UnsafeMutablePointer<DNSServiceRef?>.allocate(capacity: 1) | ||
victorherrerod marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
let replyHandlerPointer = UnsafeMutableRawPointer.allocate( | ||
victorherrerod marked this conversation as resolved.
Show resolved
Hide resolved
|
||
byteCount: MemoryLayout<QueryReplyHandler>.stride, | ||
alignment: MemoryLayout<QueryReplyHandler>.alignment | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a difference in initializing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Slight preference towards doing it in the |
||
init(handler: QueryReplyHandler) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: add newline before |
||
// Wrap 'handler' into a pointer so we can pass it to DNSServiceQueryRecord | ||
self.replyHandlerPointer.initializeMemory(as: QueryReplyHandler.self, repeating: handler, count: 1) | ||
} | ||
|
||
deinit { | ||
serviceRefPtr.deallocate() | ||
let pointer = replyHandlerPointer.assumingMemoryBound(to: QueryReplyHandler.self) | ||
pointer.deinitialize(count: 1) | ||
pointer.deallocate() | ||
} | ||
} | ||
|
||
class QueryReplyHandler { | ||
private let _handleRecord: (DNSServiceErrorType, UnsafeRawPointer?, UInt16) -> Void | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.