Skip to content

Ignore uncleanShutdown error when state is .head or .body #77

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

Merged
merged 8 commits into from
Aug 15, 2019

Conversation

vkill
Copy link
Contributor

@vkill vkill commented Aug 8, 2019

Hi there,

I think we should ignore uncleanShutdown when state is .head or .body, please review it. thx.

@swift-server-bot
Copy link

Can one of the admins verify this patch?

@Lukasa
Copy link
Collaborator

Lukasa commented Aug 8, 2019

I am...unsure about this. This is the only place where .uncleanShutdown actually does communicate useful information: specifically, that we didn't know where EOF was, and the remote server didn't use TLS to tell us, so we can't tell whether the response body got truncated or not. I don't think signalling an error here is necessary the wrong thing to do.

What it may be worth doing is making this behaviour configurable, so for users who don't care they can choose to ignore this error and always treat it as a clean EOF.

@vkill
Copy link
Contributor Author

vkill commented Aug 8, 2019

Please read testNoContentLengthWithNIOSSLUncleanShutdown and testWrongContentLengthWithNIOSSLUncleanShutdown .

We know EOF when tcp connection close even if we ignore .uncleanShutdown.

@Lukasa
Copy link
Collaborator

Lukasa commented Aug 8, 2019

No we don't, we only know that when content-length is set: that is, we know it in testWrongContentLengthWithNIOSSLUncleanShutdown, but testNoContentLengthWithNIOSSLUncleanShutdown does not error, and in my view it should.

The problem is that when content-length is not set, the complete response body is signalled by shutting the connection down as discussed in RFC 7230 § 3.3.3 Message Body Length:

  1. Otherwise, this is a response message without a declared message body length, so the message body length is determined by the number of octets received prior to the server closing the connection.

In this case, the .uncleanShutdown error is warning you that a network attacker could have injected a TCP FIN to forcibly truncate the response body. In this case, async-http-client cannot be confident that this has not happened, so we warn against it.

I am open to having settings put in place to allow you to ignore this error, but defaulting to reporting this error is the correct thing to do.

@vkill
Copy link
Contributor Author

vkill commented Aug 8, 2019

@Lukasa Yes, you are right. we can configurate it now, please continue to review.

@@ -276,19 +276,23 @@ public class HTTPClient {
public var timeout: Timeout
/// Upstream proxy, defaults to no proxy.
public var proxy: Proxy?
/// Enables ignore NIOSSLError.uncleanShutdown when state is .head or .body, defaults to `false`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need a better description. Maybe: "Ignore TLS unclean shutdown errors".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was updated.


public init(tlsConfiguration: TLSConfiguration? = nil, followRedirects: Bool = false, timeout: Timeout = Timeout(), proxy: Proxy? = nil) {
public init(tlsConfiguration: TLSConfiguration? = nil, followRedirects: Bool = false, timeout: Timeout = Timeout(), proxy: Proxy? = nil, ignoreNIOSSLUncleanShutdownError: Bool = false) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change needs to be done differently, I'm afraid. This is a breaking API change, as you are allowed to refer to functions in Swift by their complete set of labels, and this changes the set of labels here. You need to do this:

public init(tlsConfiguration: TLSConfiguration? = nil, followRedirects: Bool = false, timeout: Timeout = Timeout(), proxy: Proxy? = nil) {
    self.init(tlsConfiguration: tlsConfiguration, followRedirects: followRedirects, timeout: timeout, proxy: proxy, ignoreNIOSSLUncleanShutdownError: true)
}

public init(tlsConfiguration: TLSConfiguration? = nil, followRedirects: Bool = false, timeout: Timeout = Timeout(), proxy: Proxy? = nil, ignoreNIOSSLUncleanShutdownError: Bool) {
    self.tlsConfiguration = tlsConfiguration
    self.followRedirects = followRedirects
    self.timeout = timeout
    self.proxy = proxy
    self.ignoreNIOSSLUncleanShutdownError = ignoreNIOSSLUncleanShutdownError
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was reverted.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Lukasa we are not tagged 1.0.0 yet, do you think we can update the signature? It's unlikely imho that someone will be referencing those particular functions, wdyt?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, if we're not maintaining a stable API we can do that.

}

public init(certificateVerification: CertificateVerification, followRedirects: Bool = false, timeout: Timeout = Timeout(), proxy: Proxy? = nil) {
public init(certificateVerification: CertificateVerification, followRedirects: Bool = false, timeout: Timeout = Timeout(), proxy: Proxy? = nil, ignoreNIOSSLUncleanShutdownError: Bool = false) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This requires a similar change as the one above: we need two functions, not one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was reverted too.

/// we can also ignore this error like .end .
break
}
fallthrough
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I propose using a where clause here? That would be:

case .head where self.ignoreNIOSSLUncleanShutdownError,
     .body where self.ignoreNIOSSLUncleanShutdownError:
    break

That avoids the need for a fall through or the internal if.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's better to use internal if. it was updated.

Copy link
Collaborator

@Lukasa Lukasa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'm happy with this, but we'll need review from at least one other person.

@@ -276,19 +276,31 @@ public class HTTPClient {
public var timeout: Timeout
/// Upstream proxy, defaults to no proxy.
public var proxy: Proxy?
/// Ignore TLS unclean shutdown error, defaults to `false`.
public var ignoreNIOSSLUncleanShutdownError: Bool
Copy link
Contributor

@tomerd tomerd Aug 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

=> ignoreNIOSSLUncleanShutdownError is a mouthful imo, maybe ignoreUncleanSSLShutdown? or something even shorter if someone can come up with a suggestion?

=> what are the arguments for the default to be false vs. true?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m in favour of a shorter name.

False is a good default because we already ignore it in the cases where it’s 100% safe to do so: setting this switch to true would ignore it in the cases where it potentially opens your application up to some theoretical risks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe ignoreSSLUncleanShutdown?

Copy link
Contributor

@tomerd tomerd Aug 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ignoreUncleanSSLShutdown seems more natural? subjective obviously, so up to @artemredkin

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for ignoreUncleanSSLShutdown

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tomerd It was renamed.

@artemredkin
Copy link
Collaborator

@swift-server-bot test this please

@artemredkin artemredkin requested review from Lukasa and tomerd August 14, 2019 10:07
}

XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(httpBin.port)/wrongcontentlength").wait(), "Should fail") { error in
guard case let error = error as? HTTPParserError, case .invalidEOFState = error else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use == instead of guard case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, it's my mistakes. it was updated, and I have run tests in swift 5.0 .

}

XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(httpBin.port)/wrongcontentlength").wait(), "Should fail") { error in
guard case let error = error as? NIOSSLError, case .uncleanShutdown = error else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use == instead of guard case.

}

XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(httpBin.port)/noresponse").wait(), "Should fail") { error in
guard case let error = error as? NIOSSLError, case .uncleanShutdown = error else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use == instead of guard case.

}

XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(httpBin.port)/noresponse").wait(), "Should fail") { error in
guard case let error = error as? NIOSSLError, case .uncleanShutdown = error else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use == instead of guard case.

}

XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(httpBin.port)/nocontentlength").wait(), "Should fail") { error in
guard case let error = error as? NIOSSLError, case .uncleanShutdown = error else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use == instead of guard case.

@Lukasa
Copy link
Collaborator

Lukasa commented Aug 15, 2019

@swift-server-bot test this please

Copy link
Collaborator

@Lukasa Lukasa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@artemredkin artemredkin merged commit e0eeb04 into swift-server:master Aug 15, 2019
@artemredkin
Copy link
Collaborator

@vkill thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants