Skip to content
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

ConfigureTransport: make sure a clean DialContext is used for tcp #103

Merged
merged 2 commits into from
Oct 31, 2023

Conversation

akerouanton
Copy link
Member

@akerouanton akerouanton commented Oct 27, 2023

Since #61, there's no more DialContext defined in the default switch case of ConfigureTransport. As a consequence, if ConfigureTransport was already called with a npipe or unix proto (ie. Docker client does that to initialize its HTTP client with the default DOCKER_HOST), the DialContext function defined by these protocols will be used.

As the DialContext functions defined by unix and npipe protos totally ignore DialContext's 2nd and 3rd argument (ie. network and addr) and instead use the equivalent arguments passed to configureUnixTransport and configureNpipeTransport when they were called, this results in ConfigureTransport being totally ineffective.

In addition, DisableCompression is also reset to false.

@thaJeztah
Copy link
Member

Ci is doing funny things

Screenshot 2023-10-27 at 13 13 29

@thaJeztah
Copy link
Member

DOH! And I'm guessing we have a cert as fixture somewhere, which has expired and must be regenerated;

 config_test.go:249: Unable to verify certificate 1: x509: certificate has expired or is not yet valid

@akerouanton
Copy link
Member Author

golangci-lint errors should go away once #104 is merged.

@akerouanton
Copy link
Member Author

Based on CI error seen in moby/moby#46739, I added a commit reverting fcf9eb7.

This reverts commit fcf9eb7.

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
@@ -24,6 +25,10 @@ func ConfigureTransport(tr *http.Transport, proto, addr string) error {
return configureNpipeTransport(tr, proto, addr)
default:
tr.Proxy = http.ProxyFromEnvironment
tr.DisableCompression = false
Copy link
Member

Choose a reason for hiding this comment

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

Still looking at this one, but I don't have a good answer. The general issue here is that as you described in the commit message (e.g.) configureUnixTransport mutates this option

func configureUnixTransport(tr *http.Transport, proto, addr string) error {
if len(addr) > maxUnixSocketPathSize {
return fmt.Errorf("Unix socket path %q is too long", addr)
}
// No need for compression in local communications.
tr.DisableCompression = true
, which means it's now ambiguous if it was the caller's intent to have compression disabled, or if it was a side-effect of configureUnixTransport having been called before we reach this (and have implicitly mutated that property).

Effectively, that could also be considered a bug in the caller code, i.e., the caller code has re-used a transport, and tried to configure it multiple times. However, I don't know if there's code-paths where this happens implicitly; do you know if those are present?

If we unconditionally enable compression here, that would also mean that there's no option for a HTTP transport to disable compression, which could be a legitimate case, and may also be unexpected 🤔

But honestly, I have no good suggestion what would be best here (other than suggesting; don't re-use transport (or more factually: don't call ConfigureTransport on the same transport multiple times) if it should not be re-used.

Copy link
Member

@thaJeztah thaJeztah Oct 30, 2023

Choose a reason for hiding this comment

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

I guess the alternative could be to remove that part from configureUnixTransport, and somehow make sure that when the transport is used, the compression is disabled, but that would require changes elsewhere to keep the existing behavior.

That part was added in 88d5fb2, but it's possible that code was moved from elsewhere 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

Effectively, that could also be considered a bug in the caller code, i.e., the caller code has re-used a transport, and tried to configure it multiple times. However, I don't know if there's code-paths where this happens implicitly; do you know if those are present?

Yeah, there's one major user of that pattern: the official Docker client 🙈

I think we can't / shouldn't create a new transport in WithHost (instead of mutating the existing one), as there're at least two other option funcs that mutate the same transport:

That part was added in 88d5fb2, but it's possible that code was moved from elsewhere 🤔

It was! It comes from this commit moby/moby@fb7ceeb1. I think it makes a lot of sense to disable compression on UNIX socket, as it's only going to make the CPU spin needlessly more.

IMO it's fine to reconfigure compression when the transport protocol is changed as it's somewhat tied to it, ie. you generally want compression for HTTP over TCP, and you don't want it for HTTP over UNIX / npipe sockets.

Currently, and presumably since forever, compression has been disabled for HTTP over TCP due to the transport being initialized by defaultHTTPClient with a UNIX / npipe transport.

which means it's now ambiguous if it was the caller's intent to have compression disabled, or if it was a side-effect of configureUnixTransport having been called before we reach this (and have implicitly mutated that property).

I think it's OK to warn ConfigureTransport and WithHost callers through a doc comment that both functions will toggle compression based on what protocol is passed. If users really want to enforce that value, they should set it after these functions are called.

Copy link
Member Author

Choose a reason for hiding this comment

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

If we agree on that ⬆️, I need to update the doc comment on ConfigureTransport (and on WithHost in moby/moby).

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, there's one major user of that pattern: the official Docker client 🙈

Yes, that's what I was afraid of 😞

I think it makes a lot of sense to disable compression on UNIX socket, as it's only going to make the CPU spin needlessly more.

Yup, I definitely agree with that; it does make sense to not have compression there. The tricky bit is indeed that (per the other comment) changing that option also affects other paths 😞

IMO it's fine to reconfigure compression when the transport protocol is changed as it's somewhat tied to it, ie. you generally want compression for HTTP over TCP, and you don't want it for HTTP over UNIX / npipe sockets.

It's probably fine to keep the status quo for now (at least until we cleaned up all of this). I had feature requests, such as moby/moby#1266 in mind, where it may be desirable to disable compression (for local connections). Admitted; in that case it's mostly about compressing the layers (which would have a much bigger impact for those cases)

I think it's OK to warn ConfigureTransport and WithHost callers through a doc comment that both functions will toggle compression based on what protocol is passed. If users really want to enforce that value, they should set it after these functions are called.

Documenting definitely won't hurt. At least we shouldn't try to hide that information.

Perhaps (in future) we should look at making this all more atomic somehow (but we'd have to look what the best approach is)

Copy link
Member Author

Choose a reason for hiding this comment

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

After discussing privately this change with @thaJeztah, I'll move this change to another commit/PR to make it clear why it's changed.

Since docker#61, there's no more DialContext defined in the default switch
case of ConfigureTransport. As a consequence, if ConfigureTransport was
already called with a npipe or unix proto (ie. Docker client does that
to initialize its HTTP client with the default DOCKER_HOST), the
DialContext function defined by these protocols will be used.

As the DialContext functions defined by unix and npipe protos totally
ignore DialContext's 2nd and 3rd argument (ie. network and addr) and
instead use the equivalent arguments passed to configureUnixTransport
and configureNpipeTransport when they were called, this results in
ConfigureTransport being totally ineffective.

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
Copy link
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

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

LGTM

@thaJeztah
Copy link
Member

Thanks all! Let me bring this one in

@thaJeztah thaJeztah merged commit 12f72d4 into docker:master Oct 31, 2023
13 checks passed
@akerouanton akerouanton deleted the fix-tcp-DialContext branch October 31, 2023 16:45
akerouanton added a commit to akerouanton/go-connections that referenced this pull request Oct 31, 2023
`ConfigureTransport` is used by the official Docker client to set up its
`http.Transport`:

* [`NewClientWithOpts()`][1] starts by calling `defaultHTTPClient()`
  which creates a new `http.Transport` and call `ConfigureTransport`
  with either the default unix socket address, or the default npipe
  socket address (see [here][2]). This first call to
  `ConfigureTransport` will set `DisableCompression` to false.
* Then, when [`WithHost()`][3] is called, `ConfigureTransport` is called
  once again with the same `http.Transport` instance. If that hostURL
  uses tcp proto, we fall in `ConfigureTransport`'s default switch case,
  which doesn't reset `DisableCompression`. This effectively means that
  the Docker client is currently never using HTTP compression.

Note that `WithHost` is called whenever the `WithHostFromEnv` or
`FromEnv` option funcs are used.

We discussed creating a new `http.Transport` in Docker client codebase
whenever `ConfigureTransport` is called, but we can't do that without
introducing major side-effects (see [here][4]).

We think it _does_ make sense to keep compression disabled for unix /
npipe sockets (it would just make the CPU needlessly spin more if we
enable it), and we think it generally also makes sense to enable it for
HTTP over TCP. As such, this commit make sure compression is enabled in
such case.

If `ConfigureTransport` callers really want to make sure compression is
disabled, they should set `DisableCompression` _only after_ all
subsequent calls to `ConfigureTransport` have been made. The comment on
that function has also been updated to reflect that.

For the record, `DisableCompression = false` was first introduced in
this repo by 88d5fb2, which can be tracked down to this initial change
in moby/moby: moby/moby@fb7ceeb

[1]: https://github.com/moby/moby/blob/e9efc0a361de39903d47fe12f628c92fc095397b/client/client.go#L181-L197
[2]: https://github.com/moby/moby/blob/e9efc0a361de39903d47fe12f628c92fc095397b/client/client.go#L237-L238
[3]: https://github.com/moby/moby/blob/e9efc0a361de39903d47fe12f628c92fc095397b/client/options.go#L71-L73
[4]: docker#103 (comment)

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
akerouanton added a commit to akerouanton/go-connections that referenced this pull request Oct 31, 2023
`ConfigureTransport` is used by the official Docker client to set up its
`http.Transport`:

* [`NewClientWithOpts()`][1] starts by calling `defaultHTTPClient()`
  which creates a new `http.Transport` and call `ConfigureTransport`
  with either the default unix socket address, or the default npipe
  socket address (see [here][2]). This first call to
  `ConfigureTransport` will set `DisableCompression` to false.
* Then, when [`WithHost()`][3] is called, `ConfigureTransport` is called
  once again with the same `http.Transport` instance. If that hostURL
  uses tcp proto, we fall in `ConfigureTransport`'s default switch case,
  which doesn't reset `DisableCompression`. This effectively means that
  (presumably) **the Docker client has never been using HTTP compression
  for TCP hosts.**

Note that `WithHost` is called whenever the `WithHostFromEnv` or
`FromEnv` option funcs are used.

We think it _does_ make sense to keep compression disabled for unix /
npipe sockets (it would just make the CPU needlessly spin more if we
enable it), and we think it generally also makes sense to enable it for
HTTP over TCP.

We discussed creating a new `http.Transport` in Docker client codebase
whenever `ConfigureTransport` is called, but we can't do that without
introducing major side-effects (see [here][4]). So we still have to
leave in a world where multiple calls to `ConfigureTransport` might be
made for the same `http.Transport`.

As such, this commit make sure compression is enabled for non unix /
npipe protos.

If `ConfigureTransport` callers really want to make sure compression is
disabled, they should set `DisableCompression` _only after_ all
subsequent calls to `ConfigureTransport` have been made. The comment on
that function has also been updated to reflect that.

For the record, `DisableCompression = false` was first introduced in
this repo by 88d5fb2, which can be tracked down to this initial change
in moby/moby: moby/moby@fb7ceeb

[1]: https://github.com/moby/moby/blob/e9efc0a361de39903d47fe12f628c92fc095397b/client/client.go#L181-L197
[2]: https://github.com/moby/moby/blob/e9efc0a361de39903d47fe12f628c92fc095397b/client/client.go#L237-L238
[3]: https://github.com/moby/moby/blob/e9efc0a361de39903d47fe12f628c92fc095397b/client/options.go#L71-L73
[4]: docker#103 (comment)

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
akerouanton added a commit to akerouanton/go-connections that referenced this pull request Oct 31, 2023
`ConfigureTransport` is used by the official Docker client to set up its
`http.Transport`:

* [`NewClientWithOpts()`][1] starts by calling `defaultHTTPClient()`
  which creates a new `http.Transport` and call `ConfigureTransport`
  with either the default unix socket address, or the default npipe
  socket address (see [here][2]). This first call to
  `ConfigureTransport` will set `DisableCompression` to false.
* Then, when [`WithHost()`][3] is called, `ConfigureTransport` is called
  once again with the same `http.Transport` instance. If that hostURL
  uses tcp proto, we fall in `ConfigureTransport`'s default switch case,
  which doesn't reset `DisableCompression`. This effectively means that
  **the Docker client has (presumably) never been using HTTP compression
  for TCP hosts.**

Note that `WithHost` is called whenever the `WithHostFromEnv` or
`FromEnv` option funcs are used.

We think it _does_ make sense to keep compression disabled for unix /
npipe sockets (it would just make the CPU needlessly spin more if we
enable it), and we think it generally also makes sense to enable it for
HTTP over TCP.

We discussed creating a new `http.Transport` in Docker client codebase
whenever `ConfigureTransport` is called, but we can't do that without
introducing major side-effects (see [here][4]). So we still have to
leave in a world where multiple calls to `ConfigureTransport` might be
made for the same `http.Transport`.

As such, this commit make sure compression is enabled for non unix /
npipe protos.

If `ConfigureTransport` callers really want to make sure compression is
disabled, they should set `DisableCompression` _only after_ all
subsequent calls to `ConfigureTransport` have been made. The comment on
that function has also been updated to reflect that.

For the record, `DisableCompression = false` was first introduced in
this repo by 88d5fb2, which can be tracked down to this initial change
in moby/moby: moby/moby@fb7ceeb

[1]: https://github.com/moby/moby/blob/e9efc0a361de39903d47fe12f628c92fc095397b/client/client.go#L181-L197
[2]: https://github.com/moby/moby/blob/e9efc0a361de39903d47fe12f628c92fc095397b/client/client.go#L237-L238
[3]: https://github.com/moby/moby/blob/e9efc0a361de39903d47fe12f628c92fc095397b/client/options.go#L71-L73
[4]: docker#103 (comment)

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
akerouanton added a commit to akerouanton/go-connections that referenced this pull request Oct 31, 2023
`ConfigureTransport` is used by the official Docker client to set up its
`http.Transport`:

* [`NewClientWithOpts()`][1] starts by calling `defaultHTTPClient()`
  which creates a new `http.Transport` and call `ConfigureTransport`
  with either the default unix socket address, or the default npipe
  socket address (see [here][2]). This first call to
  `ConfigureTransport` will set `DisableCompression` to false.
* Then, when [`WithHost()`][3] is called, `ConfigureTransport` is called
  once again with the same `http.Transport` instance. If that hostURL
  uses tcp proto, we fall in `ConfigureTransport`'s default switch case,
  which doesn't reset `DisableCompression`. This effectively means that
  **the Docker client has (presumably) never been using HTTP compression
  for TCP hosts.**

Note that `WithHost` is called whenever the `WithHostFromEnv` or
`FromEnv` option funcs are used.

We think it _does_ make sense to keep compression disabled for unix /
npipe sockets (it would just make the CPU needlessly spin more if we
enable it), and we think it generally also makes sense to enable it for
HTTP over TCP.

We discussed creating a new `http.Transport` in Docker client codebase
whenever `ConfigureTransport` is called, but we can't do that without
introducing major side-effects (see [here][4]). So we still have to
leave in a world where multiple calls to `ConfigureTransport` might be
made for the same `http.Transport`.

As such, this commit make sure compression is enabled for non unix /
npipe protos.

If `ConfigureTransport` callers really want to make sure compression is
disabled, they should set `DisableCompression` _only after_ all
subsequent calls to `ConfigureTransport` have been made. The comment on
that function has also been updated to reflect that.

For the record, `DisableCompression = true` was first introduced in
this repo by 88d5fb2, which can be tracked down to this initial change
in moby/moby: moby/moby@fb7ceeb

[1]: https://github.com/moby/moby/blob/e9efc0a361de39903d47fe12f628c92fc095397b/client/client.go#L181-L197
[2]: https://github.com/moby/moby/blob/e9efc0a361de39903d47fe12f628c92fc095397b/client/client.go#L237-L238
[3]: https://github.com/moby/moby/blob/e9efc0a361de39903d47fe12f628c92fc095397b/client/options.go#L71-L73
[4]: docker#103 (comment)

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
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.

Unix transport used when HTTP host is specified
4 participants