Skip to content

Commit e840d1d

Browse files
author
Maksym Pavlenko
authored
Merge pull request containerd#10286 from dmcgowan/update-tls-fallback-default-ports
Allow fallback across default ports
2 parents 9c4ca86 + d23c4b8 commit e840d1d

File tree

6 files changed

+118
-15
lines changed

6 files changed

+118
-15
lines changed

core/remotes/docker/config/hosts.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,12 @@ import (
3030
"strings"
3131
"time"
3232

33-
"github.com/containerd/containerd/v2/core/remotes/docker"
3433
"github.com/containerd/errdefs"
3534
"github.com/containerd/log"
3635
"github.com/pelletier/go-toml/v2"
3736
tomlu "github.com/pelletier/go-toml/v2/unstable"
37+
38+
"github.com/containerd/containerd/v2/core/remotes/docker"
3839
)
3940

4041
// UpdateClientFunc is a function that lets you to amend http Client behavior used by registry clients.
@@ -250,7 +251,7 @@ func ConfigureHosts(ctx context.Context, options HostOptions) docker.RegistryHos
250251
// the request twice or consuming the request body.
251252
if host.scheme == "http" && explicitTLS {
252253
_, port, _ := net.SplitHostPort(host.host)
253-
if port != "" && port != "80" {
254+
if port != "80" {
254255
log.G(ctx).WithField("host", host.host).Info("host will try HTTPS first since it is configured for HTTP with a TLS configuration, consider changing host to HTTPS or removing unused TLS configuration")
255256
host.scheme = "https"
256257
rhosts[i].Client.Transport = docker.NewHTTPFallback(rhosts[i].Client.Transport)

core/remotes/docker/config/hosts_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ import (
2626
"path/filepath"
2727
"testing"
2828

29-
"github.com/containerd/containerd/v2/core/remotes/docker"
3029
"github.com/containerd/log/logtest"
30+
31+
"github.com/containerd/containerd/v2/core/remotes/docker"
3132
)
3233

3334
const allCaps = docker.HostCapabilityPull | docker.HostCapabilityResolve | docker.HostCapabilityPush
@@ -361,8 +362,8 @@ func TestHTTPFallback(t *testing.T) {
361362
InsecureSkipVerify: true,
362363
},
363364
},
364-
expectedScheme: "http",
365-
usesFallback: false,
365+
expectedScheme: "https",
366+
usesFallback: true,
366367
},
367368
{
368369
host: "localhost",
@@ -402,8 +403,8 @@ func TestHTTPFallback(t *testing.T) {
402403
InsecureSkipVerify: true,
403404
},
404405
},
405-
expectedScheme: "http",
406-
usesFallback: false,
406+
expectedScheme: "https",
407+
usesFallback: true,
407408
},
408409
{
409410
host: "example.com:5000",

core/remotes/docker/resolver.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,22 @@ import (
2525
"net"
2626
"net/http"
2727
"net/url"
28+
"os"
2829
"path"
2930
"strings"
3031

32+
"github.com/containerd/errdefs"
33+
"github.com/containerd/log"
34+
"github.com/opencontainers/go-digest"
35+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
36+
3137
"github.com/containerd/containerd/v2/core/images"
3238
"github.com/containerd/containerd/v2/core/remotes"
3339
"github.com/containerd/containerd/v2/core/remotes/docker/schema1" //nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
3440
remoteerrors "github.com/containerd/containerd/v2/core/remotes/errors"
3541
"github.com/containerd/containerd/v2/pkg/reference"
3642
"github.com/containerd/containerd/v2/pkg/tracing"
3743
"github.com/containerd/containerd/v2/version"
38-
"github.com/containerd/errdefs"
39-
"github.com/containerd/log"
40-
"github.com/opencontainers/go-digest"
41-
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
4244
)
4345

4446
var (
@@ -732,7 +734,7 @@ func (f *httpFallback) RoundTrip(r *http.Request) (*http.Response, error) {
732734
// only fall back if the same host had previously fell back
733735
if f.host != r.URL.Host {
734736
resp, err := f.super.RoundTrip(r)
735-
if !isTLSError(err) {
737+
if !isTLSError(err) && !isPortError(err, r.URL.Host) {
736738
return resp, err
737739
}
738740
}
@@ -773,3 +775,15 @@ func isTLSError(err error) bool {
773775

774776
return false
775777
}
778+
779+
func isPortError(err error, host string) bool {
780+
if isConnError(err) || os.IsTimeout(err) {
781+
if _, port, _ := net.SplitHostPort(host); port != "" {
782+
// Port is specified, will not retry on different port with scheme change
783+
return false
784+
}
785+
return true
786+
}
787+
788+
return false
789+
}

core/remotes/docker/resolver_test.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@ import (
3333
"testing"
3434
"time"
3535

36-
"github.com/containerd/containerd/v2/core/remotes"
37-
"github.com/containerd/containerd/v2/core/remotes/docker/auth"
38-
remoteerrors "github.com/containerd/containerd/v2/core/remotes/errors"
3936
"github.com/containerd/errdefs"
4037
digest "github.com/opencontainers/go-digest"
4138
specs "github.com/opencontainers/image-spec/specs-go"
4239
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
40+
41+
"github.com/containerd/containerd/v2/core/remotes"
42+
"github.com/containerd/containerd/v2/core/remotes/docker/auth"
43+
remoteerrors "github.com/containerd/containerd/v2/core/remotes/errors"
4344
)
4445

4546
func TestHTTPResolver(t *testing.T) {
@@ -481,6 +482,34 @@ func TestHTTPFallbackTimeoutResolver(t *testing.T) {
481482
runBasicTest(t, "testname", sf)
482483
}
483484

485+
func TestHTTPFallbackPortError(t *testing.T) {
486+
// This test only checks the isPortError since testing the whole http fallback would
487+
// require listening on 80 and making sure nothing is listening on 443.
488+
489+
l, err := net.Listen("tcp", "127.0.0.1:0")
490+
if err != nil {
491+
t.Fatal(err)
492+
}
493+
host := l.Addr().String()
494+
err = l.Close()
495+
if err != nil {
496+
t.Fatal(err)
497+
}
498+
499+
_, err = net.Dial("tcp", host)
500+
if err == nil {
501+
t.Fatal("Dial should fail after close")
502+
}
503+
504+
if isPortError(err, host) {
505+
t.Fatalf("Expected no port error for %s with %v", host, err)
506+
}
507+
if !isPortError(err, "127.0.0.1") {
508+
t.Fatalf("Expected port error for 127.0.0.1 with %v", err)
509+
}
510+
511+
}
512+
484513
func TestResolveProxy(t *testing.T) {
485514
var (
486515
ctx = context.Background()

core/remotes/docker/resolver_unix.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//go:build !windows
2+
3+
/*
4+
Copyright The containerd Authors.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
package docker
20+
21+
import (
22+
"errors"
23+
"syscall"
24+
)
25+
26+
func isConnError(err error) bool {
27+
return errors.Is(err, syscall.ECONNREFUSED)
28+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//go:build windows
2+
3+
/*
4+
Copyright The containerd Authors.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
package docker
20+
21+
import (
22+
"errors"
23+
"syscall"
24+
25+
"golang.org/x/sys/windows"
26+
)
27+
28+
func isConnError(err error) bool {
29+
return errors.Is(err, syscall.ECONNREFUSED) || errors.Is(err, windows.WSAECONNREFUSED)
30+
}

0 commit comments

Comments
 (0)