-
Notifications
You must be signed in to change notification settings - Fork 143
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
NIOSSL(Client|Server)Handler's init is throwing but that's not actionable #147
Comments
@Lukasa wdyt? |
I think I hit a similar challenge using the This is how I solved it, but having an API that works nicer with the .channelInitializer { channel in
channel.pipeline.addSSLHandlerIfNeeded(configuration: tlsConfiguration,
serverHostname: host)
} extension ChannelPipeline {
fileprivate func addSSLHandlerIfNeeded(configuration: TLSConfiguration?, serverHostname: String) -> EventLoopFuture<Void> {
guard let configuration = configuration else {
return eventLoop.makeSucceededFuture(())
}
do {
let sslContext = try NIOSSLContext(configuration: configuration)
let handler = try NIOSSLClientHandler(context: sslContext, serverHostname: serverHostname)
return addHandler(handler)
} catch {
return eventLoop.makeFailedFuture(error)
}
}
} |
Thanks @wlisac! I think there also other such APIs that we should improve on. |
The server handler can be made to stop throwing, as its only failure mode is an inability to allocate the BoringSSL object. The client handler is harder: it takes an SNI name and that can potentially be invalid. We can solve that by giving that idea a type that isn’t Do we know what the API ramifications are of removing |
Awesome.
What happens if we allowed everything? In HTTP for example, there are also clearly invalid values for for example headers and we don't handle them in any special way. You could for example add the following as a header name: I'm not really sure what to do for the SNI name, what does BoringSSL do if we pass total garbage? Couldn't we surface that as an error in the pipeline?
I have no idea, need to check. |
In my view surfacing this as an error in the pipeline is strictly worse than forcing a throwable initializer on a type: we knew well in advance that the name was bad, and we allowed it through anyway. Failing to set a correct server name leads to a guaranteed TLS handshake failure. Exactly what that will look like is a bit more difficult though. BoringSSL will accept nearly anything and will send it out, so the nature of the failure mode is dependent on the server. The most common failure mode will be a fatal alert in the handshake, followed closely in frequency by a certificate verification failure caused by the server guessing that you wanted the default host and serving that certificate. In either case, the outcome is pretty bad. |
I totally see where you're coming from and agree with most of it. The API issue however is: The user gets an error thrown in a place where they have to produce a future. I think there are two options that fit nicely into the API
There is a third option (which is what we have today) which doesn't fit into the shape of the a. Neither of which are really that great. WDYT is the best way forward? I'm still leaning towards (1) because the user has to handle handshake failure and other errors anyway. |
Sure, but why don't we just make that experience better? extension EventLoop {
func `try`<T>(_ body: () throws -> T) -> EventLoopFuture<T> {
do {
return self.makeSucceededFuture(try body())
} catch {
return self.makeFailedFuture(error)
}
}
} |
Hmm, also, how does failing in the pipeline work? We can't fail on
Otherwise we could just try to move forward, but then we are burning all of the CPU required to fully establish a pipeline and a TCP connection and part of a TLS handshake only to kill the connection. This failure mode also plays havoc with connection pooling, which will have seen a connection actually succeed before being apparently killed and so will likely retry a doomed connection. I just don't see this behaving well at all. |
Agreed. You're totally right that failing through the pipeline won't actually work (unless we delay it to handlerAdded/channelActive which sucks). Don't really have a good option. Only way out I now see is to switch from using an |
I'm certainly open to adding that |
@Lukasa I think just adding that |
Ok, a quick test with the NIO API breakage tools suggests that removing
Agreed? |
Wow, that is surprising but good :). Did the API breakage checker work with NIOSSL or did you check in a separate repo? @Lukasa I think that sounds like a plan! |
I checked with the NIO repository, just tried it on a separate throwing function there. We should get it hooked up here too really. |
Awesome, yeah. I need to find time to properly debug what’s wrong with conplicated C targets and breakage checker |
@Lukasa actually, the factory should be a |
Motivation: In apple#171 when we worked on providing access to the better verification callback, we managed to entirely miss that we had not provided that access to servers. This meant they were stuck only with the substantially-less-useful old-school callback, instead of the much better new-school one. While we're here, as we had to add multiple new initializers to NIOSSLServerHandler, I took the opportunity to also resolve the server handler portion of apple#147. The issue itself is still open because the client handlers still have throwing inits, but all "preferred" initializers on NIOSSLServerHandler no longer throw. Modifications: - Deprecated NIOSSLServerHandler.init(context:verificationCallback:) - Implemented two new initializers on NIOSSLServerHandler. - Added tests to verify that the NIOSSLServerHandler verification callback is actually called. - Removed all now-unnecessary try keywords. Result: Users will be able to provide custom verification callbacks that work much better than they currently can when on the server, and the server is now back into feature parity with the client.
Motivation: In apple#171 when we worked on providing access to the better verification callback, we managed to entirely miss that we had not provided that access to servers. This meant they were stuck only with the substantially-less-useful old-school callback, instead of the much better new-school one. While we're here, as we had to add multiple new initializers to NIOSSLServerHandler, I took the opportunity to also resolve the server handler portion of apple#147. The issue itself is still open because the client handlers still have throwing inits, but all "preferred" initializers on NIOSSLServerHandler no longer throw. Modifications: - Deprecated NIOSSLServerHandler.init(context:verificationCallback:) - Implemented two new initializers on NIOSSLServerHandler. - Added tests to verify that the NIOSSLServerHandler verification callback is actually called. - Removed all now-unnecessary try keywords. Result: Users will be able to provide custom verification callbacks that work much better than they currently can when on the server, and the server is now back into feature parity with the client.
Motivation: In #171 when we worked on providing access to the better verification callback, we managed to entirely miss that we had not provided that access to servers. This meant they were stuck only with the substantially-less-useful old-school callback, instead of the much better new-school one. While we're here, as we had to add multiple new initializers to NIOSSLServerHandler, I took the opportunity to also resolve the server handler portion of #147. The issue itself is still open because the client handlers still have throwing inits, but all "preferred" initializers on NIOSSLServerHandler no longer throw. Modifications: - Deprecated NIOSSLServerHandler.init(context:verificationCallback:) - Implemented two new initializers on NIOSSLServerHandler. - Added tests to verify that the NIOSSLServerHandler verification callback is actually called. - Removed all now-unnecessary try keywords. Result: Users will be able to provide custom verification callbacks that work much better than they currently can when on the server, and the server is now back into feature parity with the client.
NIOSSLServerHandler's init is throwing and that has two issues:
try! NIOSSLServerHandler(context: sslContext)
. If that's what we want, we can just do that in the init.clientChannelInitializer
where you have to return a future...The text was updated successfully, but these errors were encountered: