Compare commits
30 commits
Author | SHA1 | Date | |
---|---|---|---|
|
7cef0d9cd1 | ||
|
841c4c8057 | ||
|
60b9add796 | ||
|
8ad7f863b3 | ||
|
dc2755273e | ||
|
7b570f00ba | ||
|
8cdcc4f3c7 | ||
|
ed752f6544 | ||
|
9db1b6f28c | ||
|
6261281247 | ||
|
90355e5166 | ||
|
72615b19db | ||
|
23e7919e0a | ||
|
c943936458 | ||
|
8b7940f037 | ||
|
55207ea637 | ||
|
a7fa5e1deb | ||
|
8730cccee2 | ||
|
61d547bf00 | ||
|
0c9ff4ca23 | ||
|
46ca4a74b4 | ||
|
dea47c0810 | ||
|
f3842ab533 | ||
|
e0815819de | ||
|
07c797281e | ||
|
f2550b3c09 | ||
|
fc14d8f932 | ||
|
c035ef2283 | ||
|
703f14793e | ||
|
30e94dadbc |
48 changed files with 1403 additions and 420 deletions
6
.github/workflows/.test.yml
vendored
6
.github/workflows/.test.yml
vendored
|
@ -12,7 +12,7 @@ on:
|
|||
default: "graphdriver"
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.21.8"
|
||||
GO_VERSION: "1.21.9"
|
||||
GOTESTLIST_VERSION: v0.3.1
|
||||
TESTSTAT_VERSION: v0.1.25
|
||||
ITG_CLI_MATRIX_SIZE: 6
|
||||
|
@ -70,6 +70,7 @@ jobs:
|
|||
with:
|
||||
name: test-reports-unit-${{ inputs.storage }}
|
||||
path: /tmp/reports/*
|
||||
retention-days: 1
|
||||
|
||||
unit-report:
|
||||
runs-on: ubuntu-20.04
|
||||
|
@ -150,6 +151,7 @@ jobs:
|
|||
with:
|
||||
name: test-reports-docker-py-${{ inputs.storage }}
|
||||
path: /tmp/reports/*
|
||||
retention-days: 1
|
||||
|
||||
integration-flaky:
|
||||
runs-on: ubuntu-20.04
|
||||
|
@ -271,6 +273,7 @@ jobs:
|
|||
with:
|
||||
name: test-reports-integration-${{ inputs.storage }}-${{ env.TESTREPORTS_NAME }}
|
||||
path: /tmp/reports/*
|
||||
retention-days: 1
|
||||
|
||||
integration-report:
|
||||
runs-on: ubuntu-20.04
|
||||
|
@ -410,6 +413,7 @@ jobs:
|
|||
with:
|
||||
name: test-reports-integration-cli-${{ inputs.storage }}-${{ env.TESTREPORTS_NAME }}
|
||||
path: /tmp/reports/*
|
||||
retention-days: 1
|
||||
|
||||
integration-cli-report:
|
||||
runs-on: ubuntu-20.04
|
||||
|
|
4
.github/workflows/.windows.yml
vendored
4
.github/workflows/.windows.yml
vendored
|
@ -19,7 +19,7 @@ on:
|
|||
default: false
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.21.8"
|
||||
GO_VERSION: "1.21.9"
|
||||
GOTESTLIST_VERSION: v0.3.1
|
||||
TESTSTAT_VERSION: v0.1.25
|
||||
WINDOWS_BASE_IMAGE: mcr.microsoft.com/windows/servercore
|
||||
|
@ -190,6 +190,7 @@ jobs:
|
|||
with:
|
||||
name: ${{ inputs.os }}-${{ inputs.storage }}-unit-reports
|
||||
path: ${{ env.GOPATH }}\src\github.com\docker\docker\bundles\*
|
||||
retention-days: 1
|
||||
|
||||
unit-test-report:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -508,6 +509,7 @@ jobs:
|
|||
with:
|
||||
name: ${{ inputs.os }}-${{ inputs.storage }}-integration-reports-${{ matrix.runtime }}-${{ env.TESTREPORTS_NAME }}
|
||||
path: ${{ env.GOPATH }}\src\github.com\docker\docker\bundles\*
|
||||
retention-days: 1
|
||||
|
||||
integration-test-report:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
2
.github/workflows/buildkit.yml
vendored
2
.github/workflows/buildkit.yml
vendored
|
@ -13,7 +13,7 @@ on:
|
|||
pull_request:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.21.8"
|
||||
GO_VERSION: "1.21.9"
|
||||
DESTDIR: ./build
|
||||
|
||||
jobs:
|
||||
|
|
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
|
@ -51,14 +51,6 @@ jobs:
|
|||
name: Check artifacts
|
||||
run: |
|
||||
find ${{ env.DESTDIR }} -type f -exec file -e ascii -- {} +
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: ${{ env.DESTDIR }}
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
prepare-cross:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -119,11 +111,3 @@ jobs:
|
|||
name: Check artifacts
|
||||
run: |
|
||||
find ${{ env.DESTDIR }} -type f -exec file -e ascii -- {} +
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cross-${{ env.PLATFORM_PAIR }}
|
||||
path: ${{ env.DESTDIR }}
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
@ -13,7 +13,7 @@ on:
|
|||
pull_request:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.21.8"
|
||||
GO_VERSION: "1.21.9"
|
||||
GIT_PAGER: "cat"
|
||||
PAGER: "cat"
|
||||
|
||||
|
|
22
.github/workflows/validate-pr.yml
vendored
22
.github/workflows/validate-pr.yml
vendored
|
@ -11,7 +11,7 @@ jobs:
|
|||
- name: Missing `area/` label
|
||||
if: contains(join(github.event.pull_request.labels.*.name, ','), 'impact/') && !contains(join(github.event.pull_request.labels.*.name, ','), 'area/')
|
||||
run: |
|
||||
echo "Every PR with an \`impact/*\` label should also have an \`area/*\` label"
|
||||
echo "::error::Every PR with an 'impact/*' label should also have an 'area/*' label"
|
||||
exit 1
|
||||
- name: OK
|
||||
run: exit 0
|
||||
|
@ -32,15 +32,31 @@ jobs:
|
|||
desc=$(echo "$block" | awk NF)
|
||||
|
||||
if [ -z "$desc" ]; then
|
||||
echo "Changelog section is empty. Please provide a description for the changelog."
|
||||
echo "::error::Changelog section is empty. Please provide a description for the changelog."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
len=$(echo -n "$desc" | wc -c)
|
||||
if [[ $len -le 6 ]]; then
|
||||
echo "Description looks too short: $desc"
|
||||
echo "::error::Description looks too short: $desc"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "This PR will be included in the release notes with the following note:"
|
||||
echo "$desc"
|
||||
|
||||
check-pr-branch:
|
||||
runs-on: ubuntu-20.04
|
||||
env:
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
steps:
|
||||
# Backports or PR that target a release branch directly should mention the target branch in the title, for example:
|
||||
# [X.Y backport] Some change that needs backporting to X.Y
|
||||
# [X.Y] Change directly targeting the X.Y branch
|
||||
- name: Get branch from PR title
|
||||
id: title_branch
|
||||
run: echo "$PR_TITLE" | sed -n 's/^\[\([0-9]*\.[0-9]*\)[^]]*\].*/branch=\1/p' >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Check release branch
|
||||
if: github.event.pull_request.base.ref != steps.title_branch.outputs.branch && !(github.event.pull_request.base.ref == 'master' && steps.title_branch.outputs.branch == '')
|
||||
run: echo "::error::PR title suggests targetting the ${{ steps.title_branch.outputs.branch }} branch, but is opened against ${{ github.event.pull_request.base.ref }}" && exit 1
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.21.8
|
||||
ARG GO_VERSION=1.21.9
|
||||
ARG BASE_DEBIAN_DISTRO="bookworm"
|
||||
ARG GOLANG_IMAGE="golang:${GO_VERSION}-${BASE_DEBIAN_DISTRO}"
|
||||
ARG XX_VERSION=1.4.0
|
||||
|
@ -8,7 +8,7 @@ ARG XX_VERSION=1.4.0
|
|||
ARG VPNKIT_VERSION=0.5.0
|
||||
|
||||
ARG DOCKERCLI_REPOSITORY="https://github.com/docker/cli.git"
|
||||
ARG DOCKERCLI_VERSION=v26.0.0-rc2
|
||||
ARG DOCKERCLI_VERSION=v26.0.0
|
||||
# cli version used for integration-cli tests
|
||||
ARG DOCKERCLI_INTEGRATION_REPOSITORY="https://github.com/docker/cli.git"
|
||||
ARG DOCKERCLI_INTEGRATION_VERSION=v17.06.2-ce
|
||||
|
@ -198,7 +198,7 @@ RUN git init . && git remote add origin "https://github.com/containerd/container
|
|||
# When updating the binary version you may also need to update the vendor
|
||||
# version to pick up bug fixes or new APIs, however, usually the Go packages
|
||||
# are built from a commit from the master branch.
|
||||
ARG CONTAINERD_VERSION=v1.7.13
|
||||
ARG CONTAINERD_VERSION=v1.7.15
|
||||
RUN git fetch -q --depth 1 origin "${CONTAINERD_VERSION}" +refs/tags/*:refs/tags/* && git checkout -q FETCH_HEAD
|
||||
|
||||
FROM base AS containerd-build
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
# This represents the bare minimum required to build and test Docker.
|
||||
|
||||
ARG GO_VERSION=1.21.8
|
||||
ARG GO_VERSION=1.21.9
|
||||
|
||||
ARG BASE_DEBIAN_DISTRO="bookworm"
|
||||
ARG GOLANG_IMAGE="golang:${GO_VERSION}-${BASE_DEBIAN_DISTRO}"
|
||||
|
|
|
@ -161,10 +161,10 @@ FROM ${WINDOWS_BASE_IMAGE}:${WINDOWS_BASE_IMAGE_TAG}
|
|||
# Use PowerShell as the default shell
|
||||
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
|
||||
|
||||
ARG GO_VERSION=1.21.8
|
||||
ARG GO_VERSION=1.21.9
|
||||
ARG GOTESTSUM_VERSION=v1.8.2
|
||||
ARG GOWINRES_VERSION=v0.3.1
|
||||
ARG CONTAINERD_VERSION=v1.7.13
|
||||
ARG CONTAINERD_VERSION=v1.7.15
|
||||
|
||||
# Environment variable notes:
|
||||
# - GO_VERSION must be consistent with 'Dockerfile' used by Linux.
|
||||
|
|
|
@ -66,7 +66,7 @@ func getTailReader(ctx context.Context, r loggerutils.SizeReaderAt, req int) (io
|
|||
}
|
||||
|
||||
if msgLen != binary.BigEndian.Uint32(buf) {
|
||||
return nil, 0, errdefs.DataLoss(errors.Wrap(err, "log message header and footer indicate different message sizes"))
|
||||
return nil, 0, errdefs.DataLoss(errors.New("log message header and footer indicate different message sizes"))
|
||||
}
|
||||
|
||||
found++
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/docker/docker/oci/caps"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/rootless/specconv"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
volumemounts "github.com/docker/docker/volume/mounts"
|
||||
"github.com/moby/sys/mount"
|
||||
"github.com/moby/sys/mountinfo"
|
||||
|
@ -60,6 +61,28 @@ func withRlimits(daemon *Daemon, daemonCfg *dconfig.Config, c *container.Contain
|
|||
}
|
||||
}
|
||||
|
||||
// withLibnetwork sets the libnetwork hook
|
||||
func withLibnetwork(daemon *Daemon, daemonCfg *dconfig.Config, c *container.Container) coci.SpecOpts {
|
||||
return func(ctx context.Context, _ coci.Client, _ *containers.Container, s *coci.Spec) error {
|
||||
if c.Config.NetworkDisabled {
|
||||
return nil
|
||||
}
|
||||
for _, ns := range s.Linux.Namespaces {
|
||||
if ns.Type == specs.NetworkNamespace && ns.Path == "" {
|
||||
if s.Hooks == nil {
|
||||
s.Hooks = &specs.Hooks{}
|
||||
}
|
||||
shortNetCtlrID := stringid.TruncateID(daemon.netController.ID())
|
||||
s.Hooks.Prestart = append(s.Hooks.Prestart, specs.Hook{
|
||||
Path: filepath.Join("/proc", strconv.Itoa(os.Getpid()), "exe"),
|
||||
Args: []string{"libnetwork-setkey", "-exec-root=" + daemonCfg.GetExecRoot(), c.ID, shortNetCtlrID},
|
||||
})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// withRootless sets the spec to the rootless configuration
|
||||
func withRootless(daemon *Daemon, daemonCfg *dconfig.Config) coci.SpecOpts {
|
||||
return func(_ context.Context, _ coci.Client, _ *containers.Container, s *coci.Spec) error {
|
||||
|
@ -1015,6 +1038,7 @@ func (daemon *Daemon) createSpec(ctx context.Context, daemonCfg *configStore, c
|
|||
WithCapabilities(c),
|
||||
WithSeccomp(daemon, c),
|
||||
withMounts(daemon, daemonCfg, c, mounts),
|
||||
withLibnetwork(daemon, &daemonCfg.Config, c),
|
||||
WithApparmor(c),
|
||||
WithSelinux(c),
|
||||
WithOOMScore(&c.HostConfig.OomScoreAdj),
|
||||
|
|
|
@ -2,14 +2,12 @@ package daemon // import "github.com/docker/docker/daemon"
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
|
||||
"github.com/docker/docker/container"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/libcontainerd/types"
|
||||
"github.com/docker/docker/oci"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
// initializeCreatedTask performs any initialization that needs to be done to
|
||||
|
@ -22,9 +20,7 @@ func (daemon *Daemon) initializeCreatedTask(ctx context.Context, tsk types.Task,
|
|||
if err != nil {
|
||||
return errdefs.System(err)
|
||||
}
|
||||
if err := sb.SetKey(fmt.Sprintf("/proc/%d/ns/net", tsk.Pid())); err != nil {
|
||||
return errdefs.System(err)
|
||||
}
|
||||
return sb.FinishConfig()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -15,7 +15,7 @@ set -e
|
|||
# the binary version you may also need to update the vendor version to pick up
|
||||
# bug fixes or new APIs, however, usually the Go packages are built from a
|
||||
# commit from the master branch.
|
||||
: "${CONTAINERD_VERSION:=v1.7.13}"
|
||||
: "${CONTAINERD_VERSION:=v1.7.15}"
|
||||
|
||||
install_containerd() (
|
||||
echo "Install containerd version $CONTAINERD_VERSION"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.21.8
|
||||
ARG GO_VERSION=1.21.9
|
||||
ARG BASE_DEBIAN_DISTRO="bookworm"
|
||||
ARG PROTOC_VERSION=3.11.4
|
||||
|
||||
|
|
|
@ -223,8 +223,6 @@ func (s *saveSession) save(outStream io.Writer) error {
|
|||
})
|
||||
}
|
||||
|
||||
imgPlat := imageDescr.image.Platform()
|
||||
|
||||
m := ocispec.Manifest{
|
||||
Versioned: specs.Versioned{
|
||||
SchemaVersion: 2,
|
||||
|
@ -234,7 +232,6 @@ func (s *saveSession) save(outStream io.Writer) error {
|
|||
MediaType: ocispec.MediaTypeImageConfig,
|
||||
Digest: digest.Digest(imageDescr.image.ID()),
|
||||
Size: int64(len(imageDescr.image.RawJSON())),
|
||||
Platform: &imgPlat,
|
||||
},
|
||||
Layers: foreign,
|
||||
}
|
||||
|
|
66
integration/internal/network/dns.go
Normal file
66
integration/internal/network/dns.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package network
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
const DNSRespAddr = "10.11.12.13"
|
||||
|
||||
// WriteTempResolvConf writes a resolv.conf that only contains a single
|
||||
// nameserver line, with address addr.
|
||||
// It returns the name of the temp file. The temp file will be deleted
|
||||
// automatically by a t.Cleanup().
|
||||
func WriteTempResolvConf(t *testing.T, addr string) string {
|
||||
t.Helper()
|
||||
// Not using t.TempDir() here because in rootless mode, while the temporary
|
||||
// directory gets mode 0777, it's a subdir of an 0700 directory owned by root.
|
||||
// So, it's not accessible by the daemon.
|
||||
f, err := os.CreateTemp("", "resolv.conf")
|
||||
assert.NilError(t, err)
|
||||
t.Cleanup(func() { os.Remove(f.Name()) })
|
||||
err = f.Chmod(0644)
|
||||
assert.NilError(t, err)
|
||||
f.Write([]byte("nameserver " + addr + "\n"))
|
||||
return f.Name()
|
||||
}
|
||||
|
||||
// StartDaftDNS starts and returns a really, really daft DNS server that only
|
||||
// responds to type-A requests, and always with address dnsRespAddr.
|
||||
// The DNS server will be stopped automatically by a t.Cleanup().
|
||||
func StartDaftDNS(t *testing.T, addr string) {
|
||||
serveDNS := func(w dns.ResponseWriter, query *dns.Msg) {
|
||||
if query.Question[0].Qtype == dns.TypeA {
|
||||
resp := &dns.Msg{}
|
||||
resp.SetReply(query)
|
||||
answer := &dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: query.Question[0].Name,
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 600,
|
||||
},
|
||||
}
|
||||
answer.A = net.ParseIP(DNSRespAddr)
|
||||
resp.Answer = append(resp.Answer, answer)
|
||||
_ = w.WriteMsg(resp)
|
||||
}
|
||||
}
|
||||
|
||||
conn, err := net.ListenUDP("udp", &net.UDPAddr{
|
||||
IP: net.ParseIP(addr),
|
||||
Port: 53,
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
|
||||
server := &dns.Server{Handler: dns.HandlerFunc(serveDNS), PacketConn: conn}
|
||||
go func() {
|
||||
_ = server.ActivateAndServe()
|
||||
}()
|
||||
|
||||
t.Cleanup(func() { server.Shutdown() })
|
||||
}
|
|
@ -4,12 +4,12 @@ package ipvlan // import "github.com/docker/docker/integration/network/ipvlan"
|
|||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
containertypes "github.com/docker/docker/api/types/container"
|
||||
dclient "github.com/docker/docker/client"
|
||||
"github.com/docker/docker/integration/internal/container"
|
||||
net "github.com/docker/docker/integration/internal/network"
|
||||
|
@ -17,13 +17,14 @@ import (
|
|||
"github.com/docker/docker/testutil"
|
||||
"github.com/docker/docker/testutil/daemon"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/skip"
|
||||
)
|
||||
|
||||
func TestDockerNetworkIpvlanPersistance(t *testing.T) {
|
||||
// verify the driver automatically provisions the 802.1q link (di-dummy0.70)
|
||||
skip.If(t, testEnv.IsRemoteDaemon)
|
||||
skip.If(t, !ipvlanKernelSupport(t), "Kernel doesn't support ipvlan")
|
||||
skip.If(t, testEnv.IsRootless, "rootless mode has different view of network")
|
||||
|
||||
ctx := testutil.StartSpan(baseContext, t)
|
||||
|
||||
|
@ -52,7 +53,7 @@ func TestDockerNetworkIpvlanPersistance(t *testing.T) {
|
|||
|
||||
func TestDockerNetworkIpvlan(t *testing.T) {
|
||||
skip.If(t, testEnv.IsRemoteDaemon)
|
||||
skip.If(t, !ipvlanKernelSupport(t), "Kernel doesn't support ipvlan")
|
||||
skip.If(t, testEnv.IsRootless, "rootless mode has different view of network")
|
||||
|
||||
ctx := testutil.StartSpan(baseContext, t)
|
||||
|
||||
|
@ -79,14 +80,23 @@ func TestDockerNetworkIpvlan(t *testing.T) {
|
|||
name: "L3InternalMode",
|
||||
test: testIpvlanL3InternalMode,
|
||||
}, {
|
||||
name: "L2MultiSubnet",
|
||||
test: testIpvlanL2MultiSubnet,
|
||||
name: "L2MultiSubnetWithParent",
|
||||
test: testIpvlanL2MultiSubnetWithParent,
|
||||
}, {
|
||||
name: "L2MultiSubnetNoParent",
|
||||
test: testIpvlanL2MultiSubnetNoParent,
|
||||
}, {
|
||||
name: "L3MultiSubnet",
|
||||
test: testIpvlanL3MultiSubnet,
|
||||
}, {
|
||||
name: "Addressing",
|
||||
test: testIpvlanAddressing,
|
||||
name: "L2Addressing",
|
||||
test: testIpvlanL2Addressing,
|
||||
}, {
|
||||
name: "L3Addressing",
|
||||
test: testIpvlanL3Addressing,
|
||||
}, {
|
||||
name: "NoIPv6",
|
||||
test: testIpvlanNoIPv6,
|
||||
},
|
||||
} {
|
||||
|
||||
|
@ -225,10 +235,21 @@ func testIpvlanL3InternalMode(t *testing.T, ctx context.Context, client dclient.
|
|||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func testIpvlanL2MultiSubnet(t *testing.T, ctx context.Context, client dclient.APIClient) {
|
||||
func testIpvlanL2MultiSubnetWithParent(t *testing.T, ctx context.Context, client dclient.APIClient) {
|
||||
const parentIfName = "di-dummy0"
|
||||
n.CreateMasterDummy(ctx, t, parentIfName)
|
||||
defer n.DeleteInterface(ctx, t, parentIfName)
|
||||
testIpvlanL2MultiSubnet(t, ctx, client, parentIfName)
|
||||
}
|
||||
|
||||
func testIpvlanL2MultiSubnetNoParent(t *testing.T, ctx context.Context, client dclient.APIClient) {
|
||||
testIpvlanL2MultiSubnet(t, ctx, client, "")
|
||||
}
|
||||
|
||||
func testIpvlanL2MultiSubnet(t *testing.T, ctx context.Context, client dclient.APIClient, parent string) {
|
||||
netName := "dualstackl2"
|
||||
net.CreateNoError(ctx, t, client, netName,
|
||||
net.WithIPvlan("", ""),
|
||||
net.WithIPvlan(parent, ""),
|
||||
net.WithIPv6(),
|
||||
net.WithIPAM("172.28.200.0/24", ""),
|
||||
net.WithIPAM("172.28.202.0/24", "172.28.202.254"),
|
||||
|
@ -250,11 +271,22 @@ func testIpvlanL2MultiSubnet(t *testing.T, ctx context.Context, client dclient.A
|
|||
)
|
||||
c1, err := client.ContainerInspect(ctx, id1)
|
||||
assert.NilError(t, err)
|
||||
if parent == "" {
|
||||
// Inspect the v4 gateway to ensure no default GW was assigned
|
||||
assert.Check(t, is.Equal(c1.NetworkSettings.Networks[netName].Gateway, ""))
|
||||
// Inspect the v6 gateway to ensure no default GW was assigned
|
||||
assert.Check(t, is.Equal(c1.NetworkSettings.Networks[netName].IPv6Gateway, ""))
|
||||
} else {
|
||||
// Inspect the v4 gateway to ensure the proper default GW was assigned
|
||||
assert.Check(t, is.Equal(c1.NetworkSettings.Networks[netName].Gateway, "172.28.200.1"))
|
||||
// Inspect the v6 gateway to ensure the proper default GW was assigned
|
||||
assert.Check(t, is.Equal(c1.NetworkSettings.Networks[netName].IPv6Gateway, "2001:db8:abc8::1"))
|
||||
}
|
||||
|
||||
// verify ipv4 connectivity to the explicit --ipv address second to first
|
||||
// verify ipv4 connectivity to the explicit --ip address second to first
|
||||
_, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", c1.NetworkSettings.Networks[netName].IPAddress})
|
||||
assert.NilError(t, err)
|
||||
// verify ipv6 connectivity to the explicit --ipv6 address second to first
|
||||
// verify ipv6 connectivity to the explicit --ip6 address second to first
|
||||
_, err = container.Exec(ctx, client, id2, []string{"ping6", "-c", "1", c1.NetworkSettings.Networks[netName].GlobalIPv6Address})
|
||||
assert.NilError(t, err)
|
||||
|
||||
|
@ -271,22 +303,24 @@ func testIpvlanL2MultiSubnet(t *testing.T, ctx context.Context, client dclient.A
|
|||
)
|
||||
c3, err := client.ContainerInspect(ctx, id3)
|
||||
assert.NilError(t, err)
|
||||
if parent == "" {
|
||||
// Inspect the v4 gateway to ensure no default GW was assigned
|
||||
assert.Check(t, is.Equal(c3.NetworkSettings.Networks[netName].Gateway, ""))
|
||||
// Inspect the v6 gateway to ensure no default GW was assigned
|
||||
assert.Check(t, is.Equal(c3.NetworkSettings.Networks[netName].IPv6Gateway, ""))
|
||||
} else {
|
||||
// Inspect the v4 gateway to ensure the proper explicitly assigned default GW was assigned
|
||||
assert.Check(t, is.Equal(c3.NetworkSettings.Networks[netName].Gateway, "172.28.202.254"))
|
||||
// Inspect the v6 gateway to ensure the proper explicitly assigned default GW was assigned
|
||||
assert.Check(t, is.Equal(c3.NetworkSettings.Networks[netName].IPv6Gateway, "2001:db8:abc6::254"))
|
||||
}
|
||||
|
||||
// verify ipv4 connectivity to the explicit --ipv address from third to fourth
|
||||
// verify ipv4 connectivity to the explicit --ip address from third to fourth
|
||||
_, err = container.Exec(ctx, client, id4, []string{"ping", "-c", "1", c3.NetworkSettings.Networks[netName].IPAddress})
|
||||
assert.NilError(t, err)
|
||||
// verify ipv6 connectivity to the explicit --ipv6 address from third to fourth
|
||||
// verify ipv6 connectivity to the explicit --ip6 address from third to fourth
|
||||
_, err = container.Exec(ctx, client, id4, []string{"ping6", "-c", "1", c3.NetworkSettings.Networks[netName].GlobalIPv6Address})
|
||||
assert.NilError(t, err)
|
||||
|
||||
// Inspect the v4 gateway to ensure the proper default GW was assigned
|
||||
assert.Equal(t, c1.NetworkSettings.Networks[netName].Gateway, "172.28.200.1")
|
||||
// Inspect the v6 gateway to ensure the proper default GW was assigned
|
||||
assert.Equal(t, c1.NetworkSettings.Networks[netName].IPv6Gateway, "2001:db8:abc8::1")
|
||||
// Inspect the v4 gateway to ensure the proper explicitly assigned default GW was assigned
|
||||
assert.Equal(t, c3.NetworkSettings.Networks[netName].Gateway, "172.28.202.254")
|
||||
// Inspect the v6 gateway to ensure the proper explicitly assigned default GW was assigned
|
||||
assert.Equal(t, c3.NetworkSettings.Networks[netName].IPv6Gateway, "2001:db8:abc6::254")
|
||||
}
|
||||
|
||||
func testIpvlanL3MultiSubnet(t *testing.T, ctx context.Context, client dclient.APIClient) {
|
||||
|
@ -353,70 +387,162 @@ func testIpvlanL3MultiSubnet(t *testing.T, ctx context.Context, client dclient.A
|
|||
assert.Equal(t, c3.NetworkSettings.Networks[netName].IPv6Gateway, "")
|
||||
}
|
||||
|
||||
func testIpvlanAddressing(t *testing.T, ctx context.Context, client dclient.APIClient) {
|
||||
// Verify ipvlan l2 mode sets the proper default gateway routes via netlink
|
||||
// for either an explicitly set route by the user or inferred via default IPAM
|
||||
// Verify ipvlan l2 mode sets the proper default gateway routes via netlink
|
||||
// for either an explicitly set route by the user or inferred via default IPAM
|
||||
func testIpvlanL2Addressing(t *testing.T, ctx context.Context, client dclient.APIClient) {
|
||||
const parentIfName = "di-dummy0"
|
||||
n.CreateMasterDummy(ctx, t, parentIfName)
|
||||
defer n.DeleteInterface(ctx, t, parentIfName)
|
||||
|
||||
netNameL2 := "dualstackl2"
|
||||
net.CreateNoError(ctx, t, client, netNameL2,
|
||||
net.WithIPvlan("", "l2"),
|
||||
net.WithIPvlan(parentIfName, "l2"),
|
||||
net.WithIPv6(),
|
||||
net.WithIPAM("172.28.140.0/24", "172.28.140.254"),
|
||||
net.WithIPAM("2001:db8:abcb::/64", ""),
|
||||
)
|
||||
assert.Check(t, n.IsNetworkAvailable(ctx, client, netNameL2))
|
||||
|
||||
id1 := container.Run(ctx, t, client,
|
||||
id := container.Run(ctx, t, client,
|
||||
container.WithNetworkMode(netNameL2),
|
||||
)
|
||||
// Validate ipvlan l2 mode defaults gateway sets the default IPAM next-hop inferred from the subnet
|
||||
result, err := container.Exec(ctx, client, id1, []string{"ip", "route"})
|
||||
result, err := container.Exec(ctx, client, id, []string{"ip", "route"})
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, strings.Contains(result.Combined(), "default via 172.28.140.254 dev eth0"))
|
||||
assert.Check(t, is.Contains(result.Combined(), "default via 172.28.140.254 dev eth0"))
|
||||
// Validate ipvlan l2 mode sets the v6 gateway to the user specified default gateway/next-hop
|
||||
result, err = container.Exec(ctx, client, id1, []string{"ip", "-6", "route"})
|
||||
result, err = container.Exec(ctx, client, id, []string{"ip", "-6", "route"})
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, strings.Contains(result.Combined(), "default via 2001:db8:abcb::1 dev eth0"))
|
||||
assert.Check(t, is.Contains(result.Combined(), "default via 2001:db8:abcb::1 dev eth0"))
|
||||
}
|
||||
|
||||
// Validate ipvlan l3 mode sets the v4 gateway to dev eth0 and disregards any explicit or inferred next-hops
|
||||
func testIpvlanL3Addressing(t *testing.T, ctx context.Context, client dclient.APIClient) {
|
||||
const parentIfName = "di-dummy0"
|
||||
n.CreateMasterDummy(ctx, t, parentIfName)
|
||||
defer n.DeleteInterface(ctx, t, parentIfName)
|
||||
|
||||
// Validate ipvlan l3 mode sets the v4 gateway to dev eth0 and disregards any explicit or inferred next-hops
|
||||
netNameL3 := "dualstackl3"
|
||||
net.CreateNoError(ctx, t, client, netNameL3,
|
||||
net.WithIPvlan("", "l3"),
|
||||
net.WithIPvlan(parentIfName, "l3"),
|
||||
net.WithIPv6(),
|
||||
net.WithIPAM("172.28.160.0/24", "172.28.160.254"),
|
||||
net.WithIPAM("2001:db8:abcd::/64", "2001:db8:abcd::254"),
|
||||
)
|
||||
assert.Check(t, n.IsNetworkAvailable(ctx, client, netNameL3))
|
||||
|
||||
id2 := container.Run(ctx, t, client,
|
||||
id := container.Run(ctx, t, client,
|
||||
container.WithNetworkMode(netNameL3),
|
||||
)
|
||||
// Validate ipvlan l3 mode sets the v4 gateway to dev eth0 and disregards any explicit or inferred next-hops
|
||||
result, err = container.Exec(ctx, client, id2, []string{"ip", "route"})
|
||||
result, err := container.Exec(ctx, client, id, []string{"ip", "route"})
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, strings.Contains(result.Combined(), "default dev eth0"))
|
||||
assert.Check(t, is.Contains(result.Combined(), "default dev eth0"))
|
||||
// Validate ipvlan l3 mode sets the v6 gateway to dev eth0 and disregards any explicit or inferred next-hops
|
||||
result, err = container.Exec(ctx, client, id2, []string{"ip", "-6", "route"})
|
||||
result, err = container.Exec(ctx, client, id, []string{"ip", "-6", "route"})
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, strings.Contains(result.Combined(), "default dev eth0"))
|
||||
assert.Check(t, is.Contains(result.Combined(), "default dev eth0"))
|
||||
}
|
||||
|
||||
var (
|
||||
once sync.Once
|
||||
ipvlanSupported bool
|
||||
)
|
||||
// Check that an ipvlan interface with '--ipv6=false' doesn't get kernel-assigned
|
||||
// IPv6 addresses, but the loopback interface does still have an IPv6 address ('::1').
|
||||
func testIpvlanNoIPv6(t *testing.T, ctx context.Context, client dclient.APIClient) {
|
||||
const netName = "ipvlannet"
|
||||
net.CreateNoError(ctx, t, client, netName, net.WithIPvlan("", "l3"))
|
||||
assert.Check(t, n.IsNetworkAvailable(ctx, client, netName))
|
||||
|
||||
// figure out if ipvlan is supported by the kernel
|
||||
func ipvlanKernelSupport(t *testing.T) bool {
|
||||
once.Do(func() {
|
||||
// this may have the side effect of enabling the ipvlan module
|
||||
exec.Command("modprobe", "ipvlan").Run()
|
||||
_, err := os.Stat("/sys/module/ipvlan")
|
||||
if err == nil {
|
||||
ipvlanSupported = true
|
||||
} else if !os.IsNotExist(err) {
|
||||
t.Logf("WARNING: ipvlanKernelSupport: stat failed: %v\n", err)
|
||||
id := container.Run(ctx, t, client, container.WithNetworkMode(netName))
|
||||
|
||||
loRes := container.ExecT(ctx, t, client, id, []string{"ip", "a", "show", "dev", "lo"})
|
||||
assert.Check(t, is.Contains(loRes.Combined(), " inet "))
|
||||
assert.Check(t, is.Contains(loRes.Combined(), " inet6 "))
|
||||
|
||||
eth0Res := container.ExecT(ctx, t, client, id, []string{"ip", "a", "show", "dev", "eth0"})
|
||||
assert.Check(t, is.Contains(eth0Res.Combined(), " inet "))
|
||||
assert.Check(t, !strings.Contains(eth0Res.Combined(), " inet6 "),
|
||||
"result.Combined(): %s", eth0Res.Combined())
|
||||
|
||||
sysctlRes := container.ExecT(ctx, t, client, id, []string{"sysctl", "-n", "net.ipv6.conf.eth0.disable_ipv6"})
|
||||
assert.Check(t, is.Equal(strings.TrimSpace(sysctlRes.Combined()), "1"))
|
||||
}
|
||||
|
||||
// TestIPVlanDNS checks whether DNS is forwarded, for combinations of l2/l3 mode,
|
||||
// with/without a parent interface, and with '--internal'. Note that, there's no
|
||||
// attempt here to give the ipvlan network external connectivity - when this test
|
||||
// supplies a parent interface, it's a dummy. External DNS lookups only work
|
||||
// because the daemon is configured to see a host resolver on a loopback
|
||||
// interface, so the external DNS lookup happens in the host's namespace. The
|
||||
// test is checking that an automatically configured dummy interface causes the
|
||||
// network to behave as if it was '--internal'. Regression test for
|
||||
// https://github.com/moby/moby/issues/47662
|
||||
func TestIPVlanDNS(t *testing.T) {
|
||||
skip.If(t, testEnv.IsRootless, "rootless mode has different view of network")
|
||||
ctx := testutil.StartSpan(baseContext, t)
|
||||
|
||||
net.StartDaftDNS(t, "127.0.0.1")
|
||||
|
||||
tmpFileName := net.WriteTempResolvConf(t, "127.0.0.1")
|
||||
d := daemon.New(t, daemon.WithEnvVars("DOCKER_TEST_RESOLV_CONF_PATH="+tmpFileName))
|
||||
d.StartWithBusybox(ctx, t)
|
||||
t.Cleanup(func() { d.Stop(t) })
|
||||
c := d.NewClientT(t)
|
||||
|
||||
const parentIfName = "di-dummy0"
|
||||
n.CreateMasterDummy(ctx, t, parentIfName)
|
||||
defer n.DeleteInterface(ctx, t, parentIfName)
|
||||
|
||||
const netName = "ipvlan-dns-net"
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
parent string
|
||||
internal bool
|
||||
expDNS bool
|
||||
}{
|
||||
{
|
||||
name: "with parent",
|
||||
parent: parentIfName,
|
||||
// External DNS should be used (even though the network has no external connectivity).
|
||||
expDNS: true,
|
||||
},
|
||||
{
|
||||
name: "no parent",
|
||||
// External DNS should not be used, equivalent to '--internal'.
|
||||
},
|
||||
{
|
||||
name: "with parent, internal",
|
||||
parent: parentIfName,
|
||||
internal: true,
|
||||
// External DNS should not be used.
|
||||
},
|
||||
}
|
||||
|
||||
for _, mode := range []string{"l2", "l3"} {
|
||||
for _, tc := range testcases {
|
||||
name := fmt.Sprintf("Mode=%v/HasParent=%v/Internal=%v", mode, tc.parent != "", tc.internal)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ctx := testutil.StartSpan(ctx, t)
|
||||
createOpts := []func(*types.NetworkCreate){
|
||||
net.WithIPvlan(tc.parent, mode),
|
||||
}
|
||||
if tc.internal {
|
||||
createOpts = append(createOpts, net.WithInternal())
|
||||
}
|
||||
net.CreateNoError(ctx, t, c, netName, createOpts...)
|
||||
defer c.NetworkRemove(ctx, netName)
|
||||
|
||||
ctrId := container.Run(ctx, t, c, container.WithNetworkMode(netName))
|
||||
defer c.ContainerRemove(ctx, ctrId, containertypes.RemoveOptions{Force: true})
|
||||
res, err := container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"})
|
||||
assert.NilError(t, err)
|
||||
if tc.expDNS {
|
||||
assert.Check(t, is.Equal(res.ExitCode, 0))
|
||||
assert.Check(t, is.Contains(res.Stdout(), net.DNSRespAddr))
|
||||
} else {
|
||||
assert.Check(t, is.Equal(res.ExitCode, 1))
|
||||
assert.Check(t, is.Contains(res.Stdout(), "SERVFAIL"))
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return ipvlanSupported
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
containertypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/integration/internal/container"
|
||||
net "github.com/docker/docker/integration/internal/network"
|
||||
|
@ -14,6 +16,7 @@ import (
|
|||
"github.com/docker/docker/testutil"
|
||||
"github.com/docker/docker/testutil/daemon"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/skip"
|
||||
)
|
||||
|
||||
|
@ -66,11 +69,17 @@ func TestDockerNetworkMacvlan(t *testing.T) {
|
|||
name: "InternalMode",
|
||||
test: testMacvlanInternalMode,
|
||||
}, {
|
||||
name: "MultiSubnet",
|
||||
test: testMacvlanMultiSubnet,
|
||||
name: "MultiSubnetWithParent",
|
||||
test: testMacvlanMultiSubnetWithParent,
|
||||
}, {
|
||||
name: "MultiSubnetNoParent",
|
||||
test: testMacvlanMultiSubnetNoParent,
|
||||
}, {
|
||||
name: "Addressing",
|
||||
test: testMacvlanAddressing,
|
||||
}, {
|
||||
name: "NoIPv6",
|
||||
test: testMacvlanNoIPv6,
|
||||
},
|
||||
} {
|
||||
tc := tc
|
||||
|
@ -173,10 +182,21 @@ func testMacvlanInternalMode(t *testing.T, ctx context.Context, client client.AP
|
|||
assert.Check(t, err == nil)
|
||||
}
|
||||
|
||||
func testMacvlanMultiSubnet(t *testing.T, ctx context.Context, client client.APIClient) {
|
||||
func testMacvlanMultiSubnetWithParent(t *testing.T, ctx context.Context, client client.APIClient) {
|
||||
const parentIfName = "dm-dummy0"
|
||||
n.CreateMasterDummy(ctx, t, parentIfName)
|
||||
defer n.DeleteInterface(ctx, t, parentIfName)
|
||||
testMacvlanMultiSubnet(t, ctx, client, parentIfName)
|
||||
}
|
||||
|
||||
func testMacvlanMultiSubnetNoParent(t *testing.T, ctx context.Context, client client.APIClient) {
|
||||
testMacvlanMultiSubnet(t, ctx, client, "")
|
||||
}
|
||||
|
||||
func testMacvlanMultiSubnet(t *testing.T, ctx context.Context, client client.APIClient, parent string) {
|
||||
netName := "dualstackbridge"
|
||||
net.CreateNoError(ctx, t, client, netName,
|
||||
net.WithMacvlan(""),
|
||||
net.WithMacvlan(parent),
|
||||
net.WithIPv6(),
|
||||
net.WithIPAM("172.28.100.0/24", ""),
|
||||
net.WithIPAM("172.28.102.0/24", "172.28.102.254"),
|
||||
|
@ -199,11 +219,22 @@ func testMacvlanMultiSubnet(t *testing.T, ctx context.Context, client client.API
|
|||
)
|
||||
c1, err := client.ContainerInspect(ctx, id1)
|
||||
assert.NilError(t, err)
|
||||
if parent == "" {
|
||||
// Inspect the v4 gateway to ensure no default GW was assigned
|
||||
assert.Check(t, is.Equal(c1.NetworkSettings.Networks["dualstackbridge"].Gateway, ""))
|
||||
// Inspect the v6 gateway to ensure no default GW was assigned
|
||||
assert.Check(t, is.Equal(c1.NetworkSettings.Networks["dualstackbridge"].IPv6Gateway, ""))
|
||||
} else {
|
||||
// Inspect the v4 gateway to ensure the proper default GW was assigned
|
||||
assert.Check(t, is.Equal(c1.NetworkSettings.Networks["dualstackbridge"].Gateway, "172.28.100.1"))
|
||||
// Inspect the v6 gateway to ensure the proper default GW was assigned
|
||||
assert.Check(t, is.Equal(c1.NetworkSettings.Networks["dualstackbridge"].IPv6Gateway, "2001:db8:abc2::1"))
|
||||
}
|
||||
|
||||
// verify ipv4 connectivity to the explicit --ipv address second to first
|
||||
// verify ipv4 connectivity to the explicit --ip address second to first
|
||||
_, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", c1.NetworkSettings.Networks["dualstackbridge"].IPAddress})
|
||||
assert.NilError(t, err)
|
||||
// verify ipv6 connectivity to the explicit --ipv6 address second to first
|
||||
// verify ipv6 connectivity to the explicit --ip6 address second to first
|
||||
_, err = container.Exec(ctx, client, id2, []string{"ping6", "-c", "1", c1.NetworkSettings.Networks["dualstackbridge"].GlobalIPv6Address})
|
||||
assert.NilError(t, err)
|
||||
|
||||
|
@ -220,29 +251,35 @@ func testMacvlanMultiSubnet(t *testing.T, ctx context.Context, client client.API
|
|||
)
|
||||
c3, err := client.ContainerInspect(ctx, id3)
|
||||
assert.NilError(t, err)
|
||||
if parent == "" {
|
||||
// Inspect the v4 gateway to ensure no default GW was assigned
|
||||
assert.Check(t, is.Equal(c3.NetworkSettings.Networks["dualstackbridge"].Gateway, ""))
|
||||
// Inspect the v6 gateway to ensure no default GW was assigned
|
||||
assert.Check(t, is.Equal(c3.NetworkSettings.Networks["dualstackbridge"].IPv6Gateway, ""))
|
||||
} else {
|
||||
// Inspect the v4 gateway to ensure the proper explicitly assigned default GW was assigned
|
||||
assert.Check(t, is.Equal(c3.NetworkSettings.Networks["dualstackbridge"].Gateway, "172.28.102.254"))
|
||||
// Inspect the v6 gateway to ensure the proper explicitly assigned default GW was assigned
|
||||
assert.Check(t, is.Equal(c3.NetworkSettings.Networks["dualstackbridge"].IPv6Gateway, "2001:db8:abc4::254"))
|
||||
}
|
||||
|
||||
// verify ipv4 connectivity to the explicit --ipv address from third to fourth
|
||||
// verify ipv4 connectivity to the explicit --ip address from third to fourth
|
||||
_, err = container.Exec(ctx, client, id4, []string{"ping", "-c", "1", c3.NetworkSettings.Networks["dualstackbridge"].IPAddress})
|
||||
assert.NilError(t, err)
|
||||
// verify ipv6 connectivity to the explicit --ipv6 address from third to fourth
|
||||
// verify ipv6 connectivity to the explicit --ip6 address from third to fourth
|
||||
_, err = container.Exec(ctx, client, id4, []string{"ping6", "-c", "1", c3.NetworkSettings.Networks["dualstackbridge"].GlobalIPv6Address})
|
||||
assert.NilError(t, err)
|
||||
|
||||
// Inspect the v4 gateway to ensure the proper default GW was assigned
|
||||
assert.Equal(t, c1.NetworkSettings.Networks["dualstackbridge"].Gateway, "172.28.100.1")
|
||||
// Inspect the v6 gateway to ensure the proper default GW was assigned
|
||||
assert.Equal(t, c1.NetworkSettings.Networks["dualstackbridge"].IPv6Gateway, "2001:db8:abc2::1")
|
||||
// Inspect the v4 gateway to ensure the proper explicitly assigned default GW was assigned
|
||||
assert.Equal(t, c3.NetworkSettings.Networks["dualstackbridge"].Gateway, "172.28.102.254")
|
||||
// Inspect the v6 gateway to ensure the proper explicitly assigned default GW was assigned
|
||||
assert.Equal(t, c3.NetworkSettings.Networks["dualstackbridge"].IPv6Gateway, "2001:db8:abc4::254")
|
||||
}
|
||||
|
||||
func testMacvlanAddressing(t *testing.T, ctx context.Context, client client.APIClient) {
|
||||
const parentIfName = "dm-dummy0"
|
||||
n.CreateMasterDummy(ctx, t, parentIfName)
|
||||
defer n.DeleteInterface(ctx, t, parentIfName)
|
||||
|
||||
// Ensure the default gateways, next-hops and default dev devices are properly set
|
||||
netName := "dualstackbridge"
|
||||
net.CreateNoError(ctx, t, client, netName,
|
||||
net.WithMacvlan(""),
|
||||
net.WithMacvlan(parentIfName),
|
||||
net.WithIPv6(),
|
||||
net.WithOption("macvlan_mode", "bridge"),
|
||||
net.WithIPAM("172.28.130.0/24", ""),
|
||||
|
@ -263,3 +300,107 @@ func testMacvlanAddressing(t *testing.T, ctx context.Context, client client.APIC
|
|||
assert.NilError(t, err)
|
||||
assert.Check(t, strings.Contains(result.Combined(), "default via 2001:db8:abca::254 dev eth0"))
|
||||
}
|
||||
|
||||
// Check that a macvlan interface with '--ipv6=false' doesn't get kernel-assigned
|
||||
// IPv6 addresses, but the loopback interface does still have an IPv6 address ('::1').
|
||||
func testMacvlanNoIPv6(t *testing.T, ctx context.Context, client client.APIClient) {
|
||||
const netName = "macvlannet"
|
||||
|
||||
net.CreateNoError(ctx, t, client, netName,
|
||||
net.WithMacvlan(""),
|
||||
net.WithOption("macvlan_mode", "bridge"),
|
||||
)
|
||||
assert.Check(t, n.IsNetworkAvailable(ctx, client, netName))
|
||||
|
||||
id := container.Run(ctx, t, client, container.WithNetworkMode(netName))
|
||||
|
||||
loRes := container.ExecT(ctx, t, client, id, []string{"ip", "a", "show", "dev", "lo"})
|
||||
assert.Check(t, is.Contains(loRes.Combined(), " inet "))
|
||||
assert.Check(t, is.Contains(loRes.Combined(), " inet6 "))
|
||||
|
||||
eth0Res := container.ExecT(ctx, t, client, id, []string{"ip", "a", "show", "dev", "eth0"})
|
||||
assert.Check(t, is.Contains(eth0Res.Combined(), " inet "))
|
||||
assert.Check(t, !strings.Contains(eth0Res.Combined(), " inet6 "),
|
||||
"result.Combined(): %s", eth0Res.Combined())
|
||||
|
||||
sysctlRes := container.ExecT(ctx, t, client, id, []string{"sysctl", "-n", "net.ipv6.conf.eth0.disable_ipv6"})
|
||||
assert.Check(t, is.Equal(strings.TrimSpace(sysctlRes.Combined()), "1"))
|
||||
}
|
||||
|
||||
// TestMACVlanDNS checks whether DNS is forwarded, with/without a parent
|
||||
// interface, and with '--internal'. Note that there's no attempt here to give
|
||||
// the macvlan network external connectivity - when this test supplies a parent
|
||||
// interface, it's a dummy. External DNS lookups only work because the daemon is
|
||||
// configured to see a host resolver on a loopback interface, so the external DNS
|
||||
// lookup happens in the host's namespace. The test is checking that an
|
||||
// automatically configured dummy interface causes the network to behave as if it
|
||||
// was '--internal'.
|
||||
func TestMACVlanDNS(t *testing.T) {
|
||||
skip.If(t, testEnv.IsRootless, "rootless mode has different view of network")
|
||||
|
||||
ctx := testutil.StartSpan(baseContext, t)
|
||||
|
||||
net.StartDaftDNS(t, "127.0.0.1")
|
||||
|
||||
tmpFileName := net.WriteTempResolvConf(t, "127.0.0.1")
|
||||
d := daemon.New(t, daemon.WithEnvVars("DOCKER_TEST_RESOLV_CONF_PATH="+tmpFileName))
|
||||
d.StartWithBusybox(ctx, t)
|
||||
t.Cleanup(func() { d.Stop(t) })
|
||||
c := d.NewClientT(t)
|
||||
|
||||
const parentIfName = "dm-dummy0"
|
||||
n.CreateMasterDummy(ctx, t, parentIfName)
|
||||
defer n.DeleteInterface(ctx, t, parentIfName)
|
||||
|
||||
const netName = "macvlan-dns-net"
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
parent string
|
||||
internal bool
|
||||
expDNS bool
|
||||
}{
|
||||
{
|
||||
name: "with parent",
|
||||
parent: parentIfName,
|
||||
// External DNS should be used (even though the network has no external connectivity).
|
||||
expDNS: true,
|
||||
},
|
||||
{
|
||||
name: "no parent",
|
||||
// External DNS should not be used, equivalent to '--internal'.
|
||||
},
|
||||
{
|
||||
name: "with parent, internal",
|
||||
parent: parentIfName,
|
||||
internal: true,
|
||||
expDNS: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx := testutil.StartSpan(ctx, t)
|
||||
createOpts := []func(*types.NetworkCreate){
|
||||
net.WithMacvlan(tc.parent),
|
||||
}
|
||||
if tc.internal {
|
||||
createOpts = append(createOpts, net.WithInternal())
|
||||
}
|
||||
net.CreateNoError(ctx, t, c, netName, createOpts...)
|
||||
defer c.NetworkRemove(ctx, netName)
|
||||
|
||||
ctrId := container.Run(ctx, t, c, container.WithNetworkMode(netName))
|
||||
defer c.ContainerRemove(ctx, ctrId, containertypes.RemoveOptions{Force: true})
|
||||
res, err := container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"})
|
||||
assert.NilError(t, err)
|
||||
if tc.expDNS {
|
||||
assert.Check(t, is.Equal(res.ExitCode, 0))
|
||||
assert.Check(t, is.Contains(res.Stdout(), net.DNSRespAddr))
|
||||
} else {
|
||||
assert.Check(t, is.Equal(res.ExitCode, 1))
|
||||
assert.Check(t, is.Contains(res.Stdout(), "SERVFAIL"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -611,8 +612,8 @@ func TestInternalNwConnectivity(t *testing.T) {
|
|||
assert.Check(t, is.Contains(res.Stderr(), "Network is unreachable"))
|
||||
}
|
||||
|
||||
// Check that the container's interface has no IPv6 address when IPv6 is
|
||||
// disabled in a container via sysctl.
|
||||
// Check that the container's interfaces have no IPv6 address when IPv6 is
|
||||
// disabled in a container via sysctl (including 'lo').
|
||||
func TestDisableIPv6Addrs(t *testing.T) {
|
||||
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
|
||||
|
||||
|
@ -675,3 +676,65 @@ func TestDisableIPv6Addrs(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Check that an interface to an '--ipv6=false' network has no IPv6
|
||||
// address - either IPAM assigned, or kernel-assigned LL, but the loopback
|
||||
// interface does still have an IPv6 address ('::1').
|
||||
func TestNonIPv6Network(t *testing.T) {
|
||||
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
|
||||
|
||||
ctx := setupTest(t)
|
||||
d := daemon.New(t)
|
||||
d.StartWithBusybox(ctx, t)
|
||||
defer d.Stop(t)
|
||||
|
||||
c := d.NewClientT(t)
|
||||
defer c.Close()
|
||||
|
||||
const netName = "testnet"
|
||||
network.CreateNoError(ctx, t, c, netName)
|
||||
defer network.RemoveNoError(ctx, t, c, netName)
|
||||
|
||||
id := container.Run(ctx, t, c, container.WithNetworkMode(netName))
|
||||
defer c.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true})
|
||||
|
||||
loRes := container.ExecT(ctx, t, c, id, []string{"ip", "a", "show", "dev", "lo"})
|
||||
assert.Check(t, is.Contains(loRes.Combined(), " inet "))
|
||||
assert.Check(t, is.Contains(loRes.Combined(), " inet6 "))
|
||||
|
||||
eth0Res := container.ExecT(ctx, t, c, id, []string{"ip", "a", "show", "dev", "eth0"})
|
||||
assert.Check(t, is.Contains(eth0Res.Combined(), " inet "))
|
||||
assert.Check(t, !strings.Contains(eth0Res.Combined(), " inet6 "),
|
||||
"result.Combined(): %s", eth0Res.Combined())
|
||||
|
||||
sysctlRes := container.ExecT(ctx, t, c, id, []string{"sysctl", "-n", "net.ipv6.conf.eth0.disable_ipv6"})
|
||||
assert.Check(t, is.Equal(strings.TrimSpace(sysctlRes.Combined()), "1"))
|
||||
}
|
||||
|
||||
// Test that it's possible to set a sysctl on an interface in the container.
|
||||
// Regression test for https://github.com/moby/moby/issues/47619
|
||||
func TestSetInterfaceSysctl(t *testing.T) {
|
||||
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "no sysctl on Windows")
|
||||
|
||||
ctx := setupTest(t)
|
||||
d := daemon.New(t)
|
||||
d.StartWithBusybox(ctx, t)
|
||||
defer d.Stop(t)
|
||||
|
||||
c := d.NewClientT(t)
|
||||
defer c.Close()
|
||||
|
||||
const scName = "net.ipv4.conf.eth0.forwarding"
|
||||
opts := []func(config *container.TestContainerConfig){
|
||||
container.WithCmd("sysctl", scName),
|
||||
container.WithSysctls(map[string]string{scName: "1"}),
|
||||
}
|
||||
|
||||
runRes := container.RunAttach(ctx, t, c, opts...)
|
||||
defer c.ContainerRemove(ctx, runRes.ContainerID,
|
||||
containertypes.RemoveOptions{Force: true},
|
||||
)
|
||||
|
||||
stdout := runRes.Stdout.String()
|
||||
assert.Check(t, is.Contains(stdout, scName))
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package networking
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -10,29 +8,11 @@ import (
|
|||
"github.com/docker/docker/integration/internal/container"
|
||||
"github.com/docker/docker/integration/internal/network"
|
||||
"github.com/docker/docker/testutil/daemon"
|
||||
"github.com/miekg/dns"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/skip"
|
||||
)
|
||||
|
||||
// writeTempResolvConf writes a resolv.conf that only contains a single
|
||||
// nameserver line, with address addr.
|
||||
// It returns the name of the temp file.
|
||||
func writeTempResolvConf(t *testing.T, addr string) string {
|
||||
t.Helper()
|
||||
// Not using t.TempDir() here because in rootless mode, while the temporary
|
||||
// directory gets mode 0777, it's a subdir of an 0700 directory owned by root.
|
||||
// So, it's not accessible by the daemon.
|
||||
f, err := os.CreateTemp("", "resolv.conf")
|
||||
assert.NilError(t, err)
|
||||
t.Cleanup(func() { os.Remove(f.Name()) })
|
||||
err = f.Chmod(0644)
|
||||
assert.NilError(t, err)
|
||||
f.Write([]byte("nameserver " + addr + "\n"))
|
||||
return f.Name()
|
||||
}
|
||||
|
||||
// Regression test for https://github.com/moby/moby/issues/46968
|
||||
func TestResolvConfLocalhostIPv6(t *testing.T) {
|
||||
// No "/etc/resolv.conf" on Windows.
|
||||
|
@ -40,7 +20,7 @@ func TestResolvConfLocalhostIPv6(t *testing.T) {
|
|||
|
||||
ctx := setupTest(t)
|
||||
|
||||
tmpFileName := writeTempResolvConf(t, "127.0.0.53")
|
||||
tmpFileName := network.WriteTempResolvConf(t, "127.0.0.53")
|
||||
|
||||
d := daemon.New(t, daemon.WithEnvVars("DOCKER_TEST_RESOLV_CONF_PATH="+tmpFileName))
|
||||
d.StartWithBusybox(ctx, t, "--experimental", "--ip6tables")
|
||||
|
@ -81,43 +61,6 @@ options ndots:0
|
|||
`))
|
||||
}
|
||||
|
||||
const dnsRespAddr = "10.11.12.13"
|
||||
|
||||
// startDaftDNS starts and returns a really, really daft DNS server that only
|
||||
// responds to type-A requests, and always with address dnsRespAddr.
|
||||
func startDaftDNS(t *testing.T, addr string) *dns.Server {
|
||||
serveDNS := func(w dns.ResponseWriter, query *dns.Msg) {
|
||||
if query.Question[0].Qtype == dns.TypeA {
|
||||
resp := &dns.Msg{}
|
||||
resp.SetReply(query)
|
||||
answer := &dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: query.Question[0].Name,
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 600,
|
||||
},
|
||||
}
|
||||
answer.A = net.ParseIP(dnsRespAddr)
|
||||
resp.Answer = append(resp.Answer, answer)
|
||||
_ = w.WriteMsg(resp)
|
||||
}
|
||||
}
|
||||
|
||||
conn, err := net.ListenUDP("udp", &net.UDPAddr{
|
||||
IP: net.ParseIP(addr),
|
||||
Port: 53,
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
|
||||
server := &dns.Server{Handler: dns.HandlerFunc(serveDNS), PacketConn: conn}
|
||||
go func() {
|
||||
_ = server.ActivateAndServe()
|
||||
}()
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
// Check that when a container is connected to an internal network, DNS
|
||||
// requests sent to daemon's internal DNS resolver are not forwarded to
|
||||
// an upstream resolver listening on a localhost address.
|
||||
|
@ -128,11 +71,10 @@ func TestInternalNetworkDNS(t *testing.T) {
|
|||
ctx := setupTest(t)
|
||||
|
||||
// Start a DNS server on the loopback interface.
|
||||
server := startDaftDNS(t, "127.0.0.1")
|
||||
defer server.Shutdown()
|
||||
network.StartDaftDNS(t, "127.0.0.1")
|
||||
|
||||
// Set up a temp resolv.conf pointing at that DNS server, and a daemon using it.
|
||||
tmpFileName := writeTempResolvConf(t, "127.0.0.1")
|
||||
tmpFileName := network.WriteTempResolvConf(t, "127.0.0.1")
|
||||
d := daemon.New(t, daemon.WithEnvVars("DOCKER_TEST_RESOLV_CONF_PATH="+tmpFileName))
|
||||
d.StartWithBusybox(ctx, t, "--experimental", "--ip6tables")
|
||||
defer d.Stop(t)
|
||||
|
@ -160,7 +102,7 @@ func TestInternalNetworkDNS(t *testing.T) {
|
|||
res, err := container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"})
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(res.ExitCode, 0))
|
||||
assert.Check(t, is.Contains(res.Stdout(), dnsRespAddr))
|
||||
assert.Check(t, is.Contains(res.Stdout(), network.DNSRespAddr))
|
||||
|
||||
// Connect the container to the internal network as well.
|
||||
// External DNS should still be used.
|
||||
|
@ -169,7 +111,7 @@ func TestInternalNetworkDNS(t *testing.T) {
|
|||
res, err = container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"})
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(res.ExitCode, 0))
|
||||
assert.Check(t, is.Contains(res.Stdout(), dnsRespAddr))
|
||||
assert.Check(t, is.Contains(res.Stdout(), network.DNSRespAddr))
|
||||
|
||||
// Disconnect from the external network.
|
||||
// Expect no access to the external DNS.
|
||||
|
@ -187,5 +129,5 @@ func TestInternalNetworkDNS(t *testing.T) {
|
|||
res, err = container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"})
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(res.ExitCode, 0))
|
||||
assert.Check(t, is.Contains(res.Stdout(), dnsRespAddr))
|
||||
assert.Check(t, is.Contains(res.Stdout(), network.DNSRespAddr))
|
||||
}
|
||||
|
|
|
@ -122,6 +122,10 @@ func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo,
|
|||
log.G(context.TODO()).Debugf("Ipvlan Endpoint Joined with IPv6_Addr: %s IpVlan_Mode: %s, Parent: %s",
|
||||
ep.addrv6.IP.String(), n.config.IpvlanMode, n.config.Parent)
|
||||
}
|
||||
// If n.config.Internal was set locally by the driver because there's no parent
|
||||
// interface, libnetwork doesn't know the network is internal. So, stop it from
|
||||
// adding a gateway endpoint.
|
||||
jinfo.DisableGatewayService()
|
||||
}
|
||||
iNames := jinfo.InterfaceName()
|
||||
err = iNames.SetNames(vethName, containerVethPrefix)
|
||||
|
|
|
@ -41,6 +41,7 @@ func (d *driver) CreateNetwork(nid string, option map[string]interface{}, nInfo
|
|||
// if parent interface not specified, create a dummy type link to use named dummy+net_id
|
||||
if config.Parent == "" {
|
||||
config.Parent = getDummyName(stringid.TruncateID(config.ID))
|
||||
config.Internal = true
|
||||
}
|
||||
foundExisting, err := d.createNetwork(config)
|
||||
if err != nil {
|
||||
|
|
|
@ -83,6 +83,10 @@ func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo,
|
|||
log.G(context.TODO()).Debugf("Macvlan Endpoint Joined with IPv6_Addr: %s MacVlan_Mode: %s, Parent: %s",
|
||||
ep.addrv6.IP.String(), n.config.MacvlanMode, n.config.Parent)
|
||||
}
|
||||
// If n.config.Internal was set locally by the driver because there's no parent
|
||||
// interface, libnetwork doesn't know the network is internal. So, stop it from
|
||||
// adding a gateway endpoint.
|
||||
jinfo.DisableGatewayService()
|
||||
}
|
||||
iNames := jinfo.InterfaceName()
|
||||
err = iNames.SetNames(vethName, containerVethPrefix)
|
||||
|
|
|
@ -31,6 +31,7 @@ func (d *driver) CreateNetwork(nid string, option map[string]interface{}, nInfo
|
|||
// if parent interface not specified, create a dummy type link to use named dummy+net_id
|
||||
if config.Parent == "" {
|
||||
config.Parent = getDummyName(stringid.TruncateID(config.ID))
|
||||
config.Internal = true
|
||||
}
|
||||
foundExisting, err := d.createNetwork(config)
|
||||
if err != nil {
|
||||
|
|
|
@ -569,12 +569,12 @@ func (ep *Endpoint) sbJoin(sb *Sandbox, options ...EndpointOption) (err error) {
|
|||
return sb.setupDefaultGW()
|
||||
}
|
||||
|
||||
currentExtEp := sb.getGatewayEndpoint()
|
||||
// Enable upstream forwarding if the sandbox gained external connectivity.
|
||||
if sb.resolver != nil {
|
||||
sb.resolver.SetForwardingPolicy(currentExtEp != nil)
|
||||
sb.resolver.SetForwardingPolicy(sb.hasExternalAccess())
|
||||
}
|
||||
|
||||
currentExtEp := sb.getGatewayEndpoint()
|
||||
moveExtConn := currentExtEp != extEp
|
||||
if moveExtConn {
|
||||
if extEp != nil {
|
||||
|
@ -767,13 +767,13 @@ func (ep *Endpoint) sbLeave(sb *Sandbox, force bool) error {
|
|||
return sb.setupDefaultGW()
|
||||
}
|
||||
|
||||
// New endpoint providing external connectivity for the sandbox
|
||||
extEp = sb.getGatewayEndpoint()
|
||||
// Disable upstream forwarding if the sandbox lost external connectivity.
|
||||
if sb.resolver != nil {
|
||||
sb.resolver.SetForwardingPolicy(extEp != nil)
|
||||
sb.resolver.SetForwardingPolicy(sb.hasExternalAccess())
|
||||
}
|
||||
|
||||
// New endpoint providing external connectivity for the sandbox
|
||||
extEp = sb.getGatewayEndpoint()
|
||||
if moveExtConn && extEp != nil {
|
||||
log.G(context.TODO()).Debugf("Programming external connectivity on endpoint %s (%s)", extEp.Name(), extEp.ID())
|
||||
extN, err := extEp.getNetworkFromStore()
|
||||
|
|
|
@ -382,6 +382,31 @@ func (ep *Endpoint) SetGatewayIPv6(gw6 net.IP) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// hasGatewayOrDefaultRoute returns true if ep has a gateway, or a route to '0.0.0.0'/'::'.
|
||||
func (ep *Endpoint) hasGatewayOrDefaultRoute() bool {
|
||||
ep.mu.Lock()
|
||||
defer ep.mu.Unlock()
|
||||
|
||||
if ep.joinInfo != nil {
|
||||
if len(ep.joinInfo.gw) > 0 {
|
||||
return true
|
||||
}
|
||||
for _, route := range ep.joinInfo.StaticRoutes {
|
||||
if route.Destination.IP.IsUnspecified() && net.IP(route.Destination.Mask).IsUnspecified() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
if ep.iface != nil {
|
||||
for _, route := range ep.iface.routes {
|
||||
if route.IP.IsUnspecified() && net.IP(route.Mask).IsUnspecified() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ep *Endpoint) retrieveFromStore() (*Endpoint, error) {
|
||||
n, err := ep.getNetworkFromStore()
|
||||
if err != nil {
|
||||
|
|
|
@ -363,17 +363,24 @@ func setInterfaceIP(nlh *netlink.Handle, iface netlink.Link, i *Interface) error
|
|||
}
|
||||
|
||||
func setInterfaceIPv6(nlh *netlink.Handle, iface netlink.Link, i *Interface) error {
|
||||
if i.AddressIPv6() == nil {
|
||||
addr := i.AddressIPv6()
|
||||
// IPv6 must be enabled on the interface if and only if the network is
|
||||
// IPv6-enabled. For an interface on an IPv4-only network, if IPv6 isn't
|
||||
// disabled, the interface will be put into IPv6 multicast groups making
|
||||
// it unexpectedly susceptible to NDP cache poisoning, route injection, etc.
|
||||
// (At present, there will always be a pre-configured IPv6 address if the
|
||||
// network is IPv6-enabled.)
|
||||
if err := setIPv6(i.ns.path, i.DstName(), addr != nil); err != nil {
|
||||
return fmt.Errorf("failed to configure ipv6: %v", err)
|
||||
}
|
||||
if addr == nil {
|
||||
return nil
|
||||
}
|
||||
if err := checkRouteConflict(nlh, i.AddressIPv6(), netlink.FAMILY_V6); err != nil {
|
||||
if err := checkRouteConflict(nlh, addr, netlink.FAMILY_V6); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := setIPv6(i.ns.path, i.DstName(), true); err != nil {
|
||||
return fmt.Errorf("failed to enable ipv6: %v", err)
|
||||
}
|
||||
ipAddr := &netlink.Addr{IPNet: i.AddressIPv6(), Label: "", Flags: syscall.IFA_F_NODAD}
|
||||
return nlh.AddrAdd(iface, ipAddr)
|
||||
nlAddr := &netlink.Addr{IPNet: addr, Label: "", Flags: syscall.IFA_F_NODAD}
|
||||
return nlh.AddrAdd(iface, nlAddr)
|
||||
}
|
||||
|
||||
func setInterfaceLinkLocalIPs(nlh *netlink.Handle, iface netlink.Link, i *Interface) error {
|
||||
|
|
|
@ -545,26 +545,38 @@ func (n *Namespace) Restore(interfaces map[Iface][]IfaceOption, routes []*types.
|
|||
return nil
|
||||
}
|
||||
|
||||
// IPv6LoEnabled checks whether the loopback interface has an IPv6 address ('::1'
|
||||
// is assigned by the kernel if IPv6 is enabled).
|
||||
// IPv6LoEnabled returns true if the loopback interface had an IPv6 address when
|
||||
// last checked. It's always checked on the first call, and by RefreshIPv6LoEnabled.
|
||||
// ('::1' is assigned by the kernel if IPv6 is enabled.)
|
||||
func (n *Namespace) IPv6LoEnabled() bool {
|
||||
n.ipv6LoEnabledOnce.Do(func() {
|
||||
// If anything goes wrong, assume no-IPv6.
|
||||
iface, err := n.nlHandle.LinkByName("lo")
|
||||
if err != nil {
|
||||
log.G(context.TODO()).WithError(err).Warn("Unable to find 'lo' to determine IPv6 support")
|
||||
return
|
||||
}
|
||||
addrs, err := n.nlHandle.AddrList(iface, nl.FAMILY_V6)
|
||||
if err != nil {
|
||||
log.G(context.TODO()).WithError(err).Warn("Unable to get 'lo' addresses to determine IPv6 support")
|
||||
return
|
||||
}
|
||||
n.ipv6LoEnabledCached = len(addrs) > 0
|
||||
n.RefreshIPv6LoEnabled()
|
||||
})
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
return n.ipv6LoEnabledCached
|
||||
}
|
||||
|
||||
// RefreshIPv6LoEnabled refreshes the cached result returned by IPv6LoEnabled.
|
||||
func (n *Namespace) RefreshIPv6LoEnabled() {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
|
||||
// If anything goes wrong, assume no-IPv6.
|
||||
n.ipv6LoEnabledCached = false
|
||||
iface, err := n.nlHandle.LinkByName("lo")
|
||||
if err != nil {
|
||||
log.G(context.TODO()).WithError(err).Warn("Unable to find 'lo' to determine IPv6 support")
|
||||
return
|
||||
}
|
||||
addrs, err := n.nlHandle.AddrList(iface, nl.FAMILY_V6)
|
||||
if err != nil {
|
||||
log.G(context.TODO()).WithError(err).Warn("Unable to get 'lo' addresses to determine IPv6 support")
|
||||
return
|
||||
}
|
||||
n.ipv6LoEnabledCached = len(addrs) > 0
|
||||
}
|
||||
|
||||
// ApplyOSTweaks applies operating system specific knobs on the sandbox.
|
||||
func (n *Namespace) ApplyOSTweaks(types []SandboxType) {
|
||||
for _, t := range types {
|
||||
|
|
|
@ -507,6 +507,21 @@ func (sb *Sandbox) resolveName(ctx context.Context, nameOrAlias string, networkN
|
|||
return nil, ipv6Miss
|
||||
}
|
||||
|
||||
// hasExternalAccess returns true if any of sb's Endpoints appear to have external
|
||||
// network access.
|
||||
func (sb *Sandbox) hasExternalAccess() bool {
|
||||
for _, ep := range sb.Endpoints() {
|
||||
nw := ep.getNetwork()
|
||||
if nw.Internal() || nw.Type() == "null" || nw.Type() == "host" {
|
||||
continue
|
||||
}
|
||||
if ep.hasGatewayOrDefaultRoute() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// EnableService makes a managed container's service available by adding the
|
||||
// endpoint to the service load balancer and service discovery.
|
||||
func (sb *Sandbox) EnableService() (err error) {
|
||||
|
|
|
@ -48,7 +48,7 @@ func (sb *Sandbox) startResolver(restore bool) {
|
|||
// have a gateway. So, if the Sandbox is only connected to an 'internal' network,
|
||||
// it will not forward DNS requests to external resolvers. The resolver's
|
||||
// proxyDNS setting is then updated as network Endpoints are added/removed.
|
||||
sb.resolver = NewResolver(resolverIPSandbox, sb.getGatewayEndpoint() != nil, sb)
|
||||
sb.resolver = NewResolver(resolverIPSandbox, sb.hasExternalAccess(), sb)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
sb.resolver = nil
|
||||
|
|
|
@ -90,12 +90,8 @@ func (sb *Sandbox) updateGateway(ep *Endpoint) error {
|
|||
return fmt.Errorf("failed to set gateway while updating gateway: %v", err)
|
||||
}
|
||||
|
||||
// If IPv6 has been disabled in the sandbox a gateway may still have been
|
||||
// configured, don't attempt to apply it.
|
||||
if ipv6, ok := sb.ipv6Enabled(); !ok || ipv6 {
|
||||
if err := osSbox.SetGatewayIPv6(joinInfo.gw6); err != nil {
|
||||
return fmt.Errorf("failed to set IPv6 gateway while updating gateway: %v", err)
|
||||
}
|
||||
if err := osSbox.SetGatewayIPv6(joinInfo.gw6); err != nil {
|
||||
return fmt.Errorf("failed to set IPv6 gateway while updating gateway: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -162,6 +158,10 @@ func (sb *Sandbox) SetKey(basePath string) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Set up hosts and resolv.conf files. IPv6 support in the container can't be
|
||||
// determined yet, as sysctls haven't been applied by the runtime. Calling
|
||||
// FinishInit after the container task has been created, when sysctls have been
|
||||
// applied will regenerate these files.
|
||||
if err := sb.finishInitDNS(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -175,6 +175,27 @@ func (sb *Sandbox) SetKey(basePath string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// FinishConfig completes Sandbox configuration. If called after the container task has been
|
||||
// created, and sysctl settings applied, the configuration will be based on the container's
|
||||
// IPv6 support.
|
||||
func (sb *Sandbox) FinishConfig() error {
|
||||
if sb.config.useDefaultSandBox {
|
||||
return nil
|
||||
}
|
||||
|
||||
sb.mu.Lock()
|
||||
osSbox := sb.osSbox
|
||||
sb.mu.Unlock()
|
||||
if osSbox == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If sysctl changes have been made, IPv6 may have been enabled/disabled since last checked.
|
||||
osSbox.RefreshIPv6LoEnabled()
|
||||
|
||||
return sb.finishInitDNS()
|
||||
}
|
||||
|
||||
// IPv6 support can always be determined for host networking. For other network
|
||||
// types it can only be determined once there's a container namespace to probe,
|
||||
// return ok=false in that case.
|
||||
|
@ -283,12 +304,7 @@ func (sb *Sandbox) populateNetworkResources(ep *Endpoint) error {
|
|||
|
||||
ifaceOptions = append(ifaceOptions, osl.WithIPv4Address(i.addr), osl.WithRoutes(i.routes))
|
||||
if i.addrv6 != nil && i.addrv6.IP.To16() != nil {
|
||||
// If IPv6 has been disabled in the Sandbox, an IPv6 address will still have
|
||||
// been allocated. Don't apply it, because doing so would enable IPv6 on the
|
||||
// interface.
|
||||
if ipv6, ok := sb.ipv6Enabled(); !ok || ipv6 {
|
||||
ifaceOptions = append(ifaceOptions, osl.WithIPv6Address(i.addrv6))
|
||||
}
|
||||
ifaceOptions = append(ifaceOptions, osl.WithIPv6Address(i.addrv6))
|
||||
}
|
||||
if len(i.llAddrs) != 0 {
|
||||
ifaceOptions = append(ifaceOptions, osl.WithLinkLocalAddresses(i.llAddrs))
|
||||
|
|
|
@ -25,7 +25,7 @@ require (
|
|||
github.com/aws/smithy-go v1.19.0
|
||||
github.com/cloudflare/cfssl v1.6.4
|
||||
github.com/containerd/cgroups/v3 v3.0.3
|
||||
github.com/containerd/containerd v1.7.14
|
||||
github.com/containerd/containerd v1.7.15
|
||||
github.com/containerd/continuity v0.4.3
|
||||
github.com/containerd/fifo v1.1.0
|
||||
github.com/containerd/log v0.1.0
|
||||
|
@ -100,7 +100,7 @@ require (
|
|||
go.opentelemetry.io/otel/sdk v1.21.0
|
||||
go.opentelemetry.io/otel/trace v1.21.0
|
||||
golang.org/x/mod v0.13.0
|
||||
golang.org/x/net v0.18.0
|
||||
golang.org/x/net v0.23.0
|
||||
golang.org/x/sync v0.5.0
|
||||
golang.org/x/sys v0.18.0
|
||||
golang.org/x/text v0.14.0
|
||||
|
@ -212,7 +212,7 @@ require (
|
|||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
go.uber.org/zap v1.21.0 // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||
golang.org/x/oauth2 v0.11.0 // indirect
|
||||
golang.org/x/tools v0.14.0 // indirect
|
||||
|
|
16
vendor.sum
16
vendor.sum
|
@ -154,8 +154,8 @@ github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGD
|
|||
github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0=
|
||||
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
|
||||
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/containerd/containerd v1.7.14 h1:H/XLzbnGuenZEGK+v0RkwTdv2u1QFAruMe5N0GNPJwA=
|
||||
github.com/containerd/containerd v1.7.14/go.mod h1:YMC9Qt5yzNqXx/fO4j/5yYVIHXSRrlB3H7sxkUTvspg=
|
||||
github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes=
|
||||
github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY=
|
||||
github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8=
|
||||
github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
|
||||
github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY=
|
||||
|
@ -796,8 +796,8 @@ golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWP
|
|||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -858,8 +858,8 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
|
|||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -938,8 +938,8 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR
|
|||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
|
|
1
vendor/github.com/containerd/containerd/images/archive/exporter.go
generated
vendored
1
vendor/github.com/containerd/containerd/images/archive/exporter.go
generated
vendored
|
@ -471,6 +471,7 @@ func ociIndexRecord(manifests []ocispec.Descriptor) tarRecord {
|
|||
Versioned: ocispecs.Versioned{
|
||||
SchemaVersion: 2,
|
||||
},
|
||||
MediaType: ocispec.MediaTypeImageIndex,
|
||||
Manifests: manifests,
|
||||
}
|
||||
|
||||
|
|
2
vendor/github.com/containerd/containerd/version/version.go
generated
vendored
2
vendor/github.com/containerd/containerd/version/version.go
generated
vendored
|
@ -23,7 +23,7 @@ var (
|
|||
Package = "github.com/containerd/containerd"
|
||||
|
||||
// Version holds the complete version number. Filled in at linking time.
|
||||
Version = "1.7.14+unknown"
|
||||
Version = "1.7.15+unknown"
|
||||
|
||||
// Revision is filled with the VCS (e.g. git) revision being used to build
|
||||
// the program at linking time.
|
||||
|
|
39
vendor/golang.org/x/crypto/internal/poly1305/bits_compat.go
generated
vendored
39
vendor/golang.org/x/crypto/internal/poly1305/bits_compat.go
generated
vendored
|
@ -1,39 +0,0 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !go1.13
|
||||
|
||||
package poly1305
|
||||
|
||||
// Generic fallbacks for the math/bits intrinsics, copied from
|
||||
// src/math/bits/bits.go. They were added in Go 1.12, but Add64 and Sum64 had
|
||||
// variable time fallbacks until Go 1.13.
|
||||
|
||||
func bitsAdd64(x, y, carry uint64) (sum, carryOut uint64) {
|
||||
sum = x + y + carry
|
||||
carryOut = ((x & y) | ((x | y) &^ sum)) >> 63
|
||||
return
|
||||
}
|
||||
|
||||
func bitsSub64(x, y, borrow uint64) (diff, borrowOut uint64) {
|
||||
diff = x - y - borrow
|
||||
borrowOut = ((^x & y) | (^(x ^ y) & diff)) >> 63
|
||||
return
|
||||
}
|
||||
|
||||
func bitsMul64(x, y uint64) (hi, lo uint64) {
|
||||
const mask32 = 1<<32 - 1
|
||||
x0 := x & mask32
|
||||
x1 := x >> 32
|
||||
y0 := y & mask32
|
||||
y1 := y >> 32
|
||||
w0 := x0 * y0
|
||||
t := x1*y0 + w0>>32
|
||||
w1 := t & mask32
|
||||
w2 := t >> 32
|
||||
w1 += x0 * y1
|
||||
hi = x1*y1 + w2 + w1>>32
|
||||
lo = x * y
|
||||
return
|
||||
}
|
21
vendor/golang.org/x/crypto/internal/poly1305/bits_go1.13.go
generated
vendored
21
vendor/golang.org/x/crypto/internal/poly1305/bits_go1.13.go
generated
vendored
|
@ -1,21 +0,0 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.13
|
||||
|
||||
package poly1305
|
||||
|
||||
import "math/bits"
|
||||
|
||||
func bitsAdd64(x, y, carry uint64) (sum, carryOut uint64) {
|
||||
return bits.Add64(x, y, carry)
|
||||
}
|
||||
|
||||
func bitsSub64(x, y, borrow uint64) (diff, borrowOut uint64) {
|
||||
return bits.Sub64(x, y, borrow)
|
||||
}
|
||||
|
||||
func bitsMul64(x, y uint64) (hi, lo uint64) {
|
||||
return bits.Mul64(x, y)
|
||||
}
|
43
vendor/golang.org/x/crypto/internal/poly1305/sum_generic.go
generated
vendored
43
vendor/golang.org/x/crypto/internal/poly1305/sum_generic.go
generated
vendored
|
@ -7,7 +7,10 @@
|
|||
|
||||
package poly1305
|
||||
|
||||
import "encoding/binary"
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
// Poly1305 [RFC 7539] is a relatively simple algorithm: the authentication tag
|
||||
// for a 64 bytes message is approximately
|
||||
|
@ -114,13 +117,13 @@ type uint128 struct {
|
|||
}
|
||||
|
||||
func mul64(a, b uint64) uint128 {
|
||||
hi, lo := bitsMul64(a, b)
|
||||
hi, lo := bits.Mul64(a, b)
|
||||
return uint128{lo, hi}
|
||||
}
|
||||
|
||||
func add128(a, b uint128) uint128 {
|
||||
lo, c := bitsAdd64(a.lo, b.lo, 0)
|
||||
hi, c := bitsAdd64(a.hi, b.hi, c)
|
||||
lo, c := bits.Add64(a.lo, b.lo, 0)
|
||||
hi, c := bits.Add64(a.hi, b.hi, c)
|
||||
if c != 0 {
|
||||
panic("poly1305: unexpected overflow")
|
||||
}
|
||||
|
@ -155,8 +158,8 @@ func updateGeneric(state *macState, msg []byte) {
|
|||
// hide leading zeroes. For full chunks, that's 1 << 128, so we can just
|
||||
// add 1 to the most significant (2¹²⁸) limb, h2.
|
||||
if len(msg) >= TagSize {
|
||||
h0, c = bitsAdd64(h0, binary.LittleEndian.Uint64(msg[0:8]), 0)
|
||||
h1, c = bitsAdd64(h1, binary.LittleEndian.Uint64(msg[8:16]), c)
|
||||
h0, c = bits.Add64(h0, binary.LittleEndian.Uint64(msg[0:8]), 0)
|
||||
h1, c = bits.Add64(h1, binary.LittleEndian.Uint64(msg[8:16]), c)
|
||||
h2 += c + 1
|
||||
|
||||
msg = msg[TagSize:]
|
||||
|
@ -165,8 +168,8 @@ func updateGeneric(state *macState, msg []byte) {
|
|||
copy(buf[:], msg)
|
||||
buf[len(msg)] = 1
|
||||
|
||||
h0, c = bitsAdd64(h0, binary.LittleEndian.Uint64(buf[0:8]), 0)
|
||||
h1, c = bitsAdd64(h1, binary.LittleEndian.Uint64(buf[8:16]), c)
|
||||
h0, c = bits.Add64(h0, binary.LittleEndian.Uint64(buf[0:8]), 0)
|
||||
h1, c = bits.Add64(h1, binary.LittleEndian.Uint64(buf[8:16]), c)
|
||||
h2 += c
|
||||
|
||||
msg = nil
|
||||
|
@ -219,9 +222,9 @@ func updateGeneric(state *macState, msg []byte) {
|
|||
m3 := h2r1
|
||||
|
||||
t0 := m0.lo
|
||||
t1, c := bitsAdd64(m1.lo, m0.hi, 0)
|
||||
t2, c := bitsAdd64(m2.lo, m1.hi, c)
|
||||
t3, _ := bitsAdd64(m3.lo, m2.hi, c)
|
||||
t1, c := bits.Add64(m1.lo, m0.hi, 0)
|
||||
t2, c := bits.Add64(m2.lo, m1.hi, c)
|
||||
t3, _ := bits.Add64(m3.lo, m2.hi, c)
|
||||
|
||||
// Now we have the result as 4 64-bit limbs, and we need to reduce it
|
||||
// modulo 2¹³⁰ - 5. The special shape of this Crandall prime lets us do
|
||||
|
@ -243,14 +246,14 @@ func updateGeneric(state *macState, msg []byte) {
|
|||
|
||||
// To add c * 5 to h, we first add cc = c * 4, and then add (cc >> 2) = c.
|
||||
|
||||
h0, c = bitsAdd64(h0, cc.lo, 0)
|
||||
h1, c = bitsAdd64(h1, cc.hi, c)
|
||||
h0, c = bits.Add64(h0, cc.lo, 0)
|
||||
h1, c = bits.Add64(h1, cc.hi, c)
|
||||
h2 += c
|
||||
|
||||
cc = shiftRightBy2(cc)
|
||||
|
||||
h0, c = bitsAdd64(h0, cc.lo, 0)
|
||||
h1, c = bitsAdd64(h1, cc.hi, c)
|
||||
h0, c = bits.Add64(h0, cc.lo, 0)
|
||||
h1, c = bits.Add64(h1, cc.hi, c)
|
||||
h2 += c
|
||||
|
||||
// h2 is at most 3 + 1 + 1 = 5, making the whole of h at most
|
||||
|
@ -287,9 +290,9 @@ func finalize(out *[TagSize]byte, h *[3]uint64, s *[2]uint64) {
|
|||
// in constant time, we compute t = h - (2¹³⁰ - 5), and select h as the
|
||||
// result if the subtraction underflows, and t otherwise.
|
||||
|
||||
hMinusP0, b := bitsSub64(h0, p0, 0)
|
||||
hMinusP1, b := bitsSub64(h1, p1, b)
|
||||
_, b = bitsSub64(h2, p2, b)
|
||||
hMinusP0, b := bits.Sub64(h0, p0, 0)
|
||||
hMinusP1, b := bits.Sub64(h1, p1, b)
|
||||
_, b = bits.Sub64(h2, p2, b)
|
||||
|
||||
// h = h if h < p else h - p
|
||||
h0 = select64(b, h0, hMinusP0)
|
||||
|
@ -301,8 +304,8 @@ func finalize(out *[TagSize]byte, h *[3]uint64, s *[2]uint64) {
|
|||
//
|
||||
// by just doing a wide addition with the 128 low bits of h and discarding
|
||||
// the overflow.
|
||||
h0, c := bitsAdd64(h0, s[0], 0)
|
||||
h1, _ = bitsAdd64(h1, s[1], c)
|
||||
h0, c := bits.Add64(h0, s[0], 0)
|
||||
h1, _ = bits.Add64(h1, s[1], c)
|
||||
|
||||
binary.LittleEndian.PutUint64(out[0:8], h0)
|
||||
binary.LittleEndian.PutUint64(out[8:16], h1)
|
||||
|
|
14
vendor/golang.org/x/crypto/internal/poly1305/sum_ppc64le.s
generated
vendored
14
vendor/golang.org/x/crypto/internal/poly1305/sum_ppc64le.s
generated
vendored
|
@ -19,15 +19,14 @@
|
|||
|
||||
#define POLY1305_MUL(h0, h1, h2, r0, r1, t0, t1, t2, t3, t4, t5) \
|
||||
MULLD r0, h0, t0; \
|
||||
MULLD r0, h1, t4; \
|
||||
MULHDU r0, h0, t1; \
|
||||
MULLD r0, h1, t4; \
|
||||
MULHDU r0, h1, t5; \
|
||||
ADDC t4, t1, t1; \
|
||||
MULLD r0, h2, t2; \
|
||||
ADDZE t5; \
|
||||
MULHDU r1, h0, t4; \
|
||||
MULLD r1, h0, h0; \
|
||||
ADD t5, t2, t2; \
|
||||
ADDE t5, t2, t2; \
|
||||
ADDC h0, t1, t1; \
|
||||
MULLD h2, r1, t3; \
|
||||
ADDZE t4, h0; \
|
||||
|
@ -37,13 +36,11 @@
|
|||
ADDE t5, t3, t3; \
|
||||
ADDC h0, t2, t2; \
|
||||
MOVD $-4, t4; \
|
||||
MOVD t0, h0; \
|
||||
MOVD t1, h1; \
|
||||
ADDZE t3; \
|
||||
ANDCC $3, t2, h2; \
|
||||
AND t2, t4, t0; \
|
||||
RLDICL $0, t2, $62, h2; \
|
||||
AND t2, t4, h0; \
|
||||
ADDC t0, h0, h0; \
|
||||
ADDE t3, h1, h1; \
|
||||
ADDE t3, t1, h1; \
|
||||
SLD $62, t3, t4; \
|
||||
SRD $2, t2; \
|
||||
ADDZE h2; \
|
||||
|
@ -75,6 +72,7 @@ TEXT ·update(SB), $0-32
|
|||
loop:
|
||||
POLY1305_ADD(R4, R8, R9, R10, R20, R21, R22)
|
||||
|
||||
PCALIGN $16
|
||||
multiply:
|
||||
POLY1305_MUL(R8, R9, R10, R11, R12, R16, R17, R18, R14, R20, R21)
|
||||
ADD $-16, R5
|
||||
|
|
13
vendor/golang.org/x/crypto/ocsp/ocsp.go
generated
vendored
13
vendor/golang.org/x/crypto/ocsp/ocsp.go
generated
vendored
|
@ -279,21 +279,22 @@ func getOIDFromHashAlgorithm(target crypto.Hash) asn1.ObjectIdentifier {
|
|||
|
||||
// This is the exposed reflection of the internal OCSP structures.
|
||||
|
||||
// The status values that can be expressed in OCSP. See RFC 6960.
|
||||
// The status values that can be expressed in OCSP. See RFC 6960.
|
||||
// These are used for the Response.Status field.
|
||||
const (
|
||||
// Good means that the certificate is valid.
|
||||
Good = iota
|
||||
Good = 0
|
||||
// Revoked means that the certificate has been deliberately revoked.
|
||||
Revoked
|
||||
Revoked = 1
|
||||
// Unknown means that the OCSP responder doesn't know about the certificate.
|
||||
Unknown
|
||||
Unknown = 2
|
||||
// ServerFailed is unused and was never used (see
|
||||
// https://go-review.googlesource.com/#/c/18944). ParseResponse will
|
||||
// return a ResponseError when an error response is parsed.
|
||||
ServerFailed
|
||||
ServerFailed = 3
|
||||
)
|
||||
|
||||
// The enumerated reasons for revoking a certificate. See RFC 5280.
|
||||
// The enumerated reasons for revoking a certificate. See RFC 5280.
|
||||
const (
|
||||
Unspecified = 0
|
||||
KeyCompromise = 1
|
||||
|
|
42
vendor/golang.org/x/net/http2/frame.go
generated
vendored
42
vendor/golang.org/x/net/http2/frame.go
generated
vendored
|
@ -1510,13 +1510,12 @@ func (mh *MetaHeadersFrame) checkPseudos() error {
|
|||
}
|
||||
|
||||
func (fr *Framer) maxHeaderStringLen() int {
|
||||
v := fr.maxHeaderListSize()
|
||||
if uint32(int(v)) == v {
|
||||
return int(v)
|
||||
v := int(fr.maxHeaderListSize())
|
||||
if v < 0 {
|
||||
// If maxHeaderListSize overflows an int, use no limit (0).
|
||||
return 0
|
||||
}
|
||||
// They had a crazy big number for MaxHeaderBytes anyway,
|
||||
// so give them unlimited header lengths:
|
||||
return 0
|
||||
return v
|
||||
}
|
||||
|
||||
// readMetaFrame returns 0 or more CONTINUATION frames from fr and
|
||||
|
@ -1565,6 +1564,7 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
|
|||
if size > remainSize {
|
||||
hdec.SetEmitEnabled(false)
|
||||
mh.Truncated = true
|
||||
remainSize = 0
|
||||
return
|
||||
}
|
||||
remainSize -= size
|
||||
|
@ -1577,6 +1577,36 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
|
|||
var hc headersOrContinuation = hf
|
||||
for {
|
||||
frag := hc.HeaderBlockFragment()
|
||||
|
||||
// Avoid parsing large amounts of headers that we will then discard.
|
||||
// If the sender exceeds the max header list size by too much,
|
||||
// skip parsing the fragment and close the connection.
|
||||
//
|
||||
// "Too much" is either any CONTINUATION frame after we've already
|
||||
// exceeded the max header list size (in which case remainSize is 0),
|
||||
// or a frame whose encoded size is more than twice the remaining
|
||||
// header list bytes we're willing to accept.
|
||||
if int64(len(frag)) > int64(2*remainSize) {
|
||||
if VerboseLogs {
|
||||
log.Printf("http2: header list too large")
|
||||
}
|
||||
// It would be nice to send a RST_STREAM before sending the GOAWAY,
|
||||
// but the structure of the server's frame writer makes this difficult.
|
||||
return nil, ConnectionError(ErrCodeProtocol)
|
||||
}
|
||||
|
||||
// Also close the connection after any CONTINUATION frame following an
|
||||
// invalid header, since we stop tracking the size of the headers after
|
||||
// an invalid one.
|
||||
if invalid != nil {
|
||||
if VerboseLogs {
|
||||
log.Printf("http2: invalid header: %v", invalid)
|
||||
}
|
||||
// It would be nice to send a RST_STREAM before sending the GOAWAY,
|
||||
// but the structure of the server's frame writer makes this difficult.
|
||||
return nil, ConnectionError(ErrCodeProtocol)
|
||||
}
|
||||
|
||||
if _, err := hdec.Write(frag); err != nil {
|
||||
return nil, ConnectionError(ErrCodeCompression)
|
||||
}
|
||||
|
|
11
vendor/golang.org/x/net/http2/pipe.go
generated
vendored
11
vendor/golang.org/x/net/http2/pipe.go
generated
vendored
|
@ -77,7 +77,10 @@ func (p *pipe) Read(d []byte) (n int, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
var errClosedPipeWrite = errors.New("write on closed buffer")
|
||||
var (
|
||||
errClosedPipeWrite = errors.New("write on closed buffer")
|
||||
errUninitializedPipeWrite = errors.New("write on uninitialized buffer")
|
||||
)
|
||||
|
||||
// Write copies bytes from p into the buffer and wakes a reader.
|
||||
// It is an error to write more data than the buffer can hold.
|
||||
|
@ -91,6 +94,12 @@ func (p *pipe) Write(d []byte) (n int, err error) {
|
|||
if p.err != nil || p.breakErr != nil {
|
||||
return 0, errClosedPipeWrite
|
||||
}
|
||||
// pipe.setBuffer is never invoked, leaving the buffer uninitialized.
|
||||
// We shouldn't try to write to an uninitialized pipe,
|
||||
// but returning an error is better than panicking.
|
||||
if p.b == nil {
|
||||
return 0, errUninitializedPipeWrite
|
||||
}
|
||||
return p.b.Write(d)
|
||||
}
|
||||
|
||||
|
|
13
vendor/golang.org/x/net/http2/server.go
generated
vendored
13
vendor/golang.org/x/net/http2/server.go
generated
vendored
|
@ -124,6 +124,7 @@ type Server struct {
|
|||
// IdleTimeout specifies how long until idle clients should be
|
||||
// closed with a GOAWAY frame. PING frames are not considered
|
||||
// activity for the purposes of IdleTimeout.
|
||||
// If zero or negative, there is no timeout.
|
||||
IdleTimeout time.Duration
|
||||
|
||||
// MaxUploadBufferPerConnection is the size of the initial flow
|
||||
|
@ -434,7 +435,7 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
|
|||
// passes the connection off to us with the deadline already set.
|
||||
// Write deadlines are set per stream in serverConn.newStream.
|
||||
// Disarm the net.Conn write deadline here.
|
||||
if sc.hs.WriteTimeout != 0 {
|
||||
if sc.hs.WriteTimeout > 0 {
|
||||
sc.conn.SetWriteDeadline(time.Time{})
|
||||
}
|
||||
|
||||
|
@ -924,7 +925,7 @@ func (sc *serverConn) serve() {
|
|||
sc.setConnState(http.StateActive)
|
||||
sc.setConnState(http.StateIdle)
|
||||
|
||||
if sc.srv.IdleTimeout != 0 {
|
||||
if sc.srv.IdleTimeout > 0 {
|
||||
sc.idleTimer = time.AfterFunc(sc.srv.IdleTimeout, sc.onIdleTimer)
|
||||
defer sc.idleTimer.Stop()
|
||||
}
|
||||
|
@ -1637,7 +1638,7 @@ func (sc *serverConn) closeStream(st *stream, err error) {
|
|||
delete(sc.streams, st.id)
|
||||
if len(sc.streams) == 0 {
|
||||
sc.setConnState(http.StateIdle)
|
||||
if sc.srv.IdleTimeout != 0 {
|
||||
if sc.srv.IdleTimeout > 0 {
|
||||
sc.idleTimer.Reset(sc.srv.IdleTimeout)
|
||||
}
|
||||
if h1ServerKeepAlivesDisabled(sc.hs) {
|
||||
|
@ -2017,7 +2018,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
|
|||
// similar to how the http1 server works. Here it's
|
||||
// technically more like the http1 Server's ReadHeaderTimeout
|
||||
// (in Go 1.8), though. That's a more sane option anyway.
|
||||
if sc.hs.ReadTimeout != 0 {
|
||||
if sc.hs.ReadTimeout > 0 {
|
||||
sc.conn.SetReadDeadline(time.Time{})
|
||||
st.readDeadline = time.AfterFunc(sc.hs.ReadTimeout, st.onReadTimeout)
|
||||
}
|
||||
|
@ -2038,7 +2039,7 @@ func (sc *serverConn) upgradeRequest(req *http.Request) {
|
|||
|
||||
// Disable any read deadline set by the net/http package
|
||||
// prior to the upgrade.
|
||||
if sc.hs.ReadTimeout != 0 {
|
||||
if sc.hs.ReadTimeout > 0 {
|
||||
sc.conn.SetReadDeadline(time.Time{})
|
||||
}
|
||||
|
||||
|
@ -2116,7 +2117,7 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream
|
|||
st.flow.conn = &sc.flow // link to conn-level counter
|
||||
st.flow.add(sc.initialStreamSendWindowSize)
|
||||
st.inflow.init(sc.srv.initialStreamRecvWindowSize())
|
||||
if sc.hs.WriteTimeout != 0 {
|
||||
if sc.hs.WriteTimeout > 0 {
|
||||
st.writeDeadline = time.AfterFunc(sc.hs.WriteTimeout, st.onWriteTimeout)
|
||||
}
|
||||
|
||||
|
|
331
vendor/golang.org/x/net/http2/testsync.go
generated
vendored
Normal file
331
vendor/golang.org/x/net/http2/testsync.go
generated
vendored
Normal file
|
@ -0,0 +1,331 @@
|
|||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
package http2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// testSyncHooks coordinates goroutines in tests.
|
||||
//
|
||||
// For example, a call to ClientConn.RoundTrip involves several goroutines, including:
|
||||
// - the goroutine running RoundTrip;
|
||||
// - the clientStream.doRequest goroutine, which writes the request; and
|
||||
// - the clientStream.readLoop goroutine, which reads the response.
|
||||
//
|
||||
// Using testSyncHooks, a test can start a RoundTrip and identify when all these goroutines
|
||||
// are blocked waiting for some condition such as reading the Request.Body or waiting for
|
||||
// flow control to become available.
|
||||
//
|
||||
// The testSyncHooks also manage timers and synthetic time in tests.
|
||||
// This permits us to, for example, start a request and cause it to time out waiting for
|
||||
// response headers without resorting to time.Sleep calls.
|
||||
type testSyncHooks struct {
|
||||
// active/inactive act as a mutex and condition variable.
|
||||
//
|
||||
// - neither chan contains a value: testSyncHooks is locked.
|
||||
// - active contains a value: unlocked, and at least one goroutine is not blocked
|
||||
// - inactive contains a value: unlocked, and all goroutines are blocked
|
||||
active chan struct{}
|
||||
inactive chan struct{}
|
||||
|
||||
// goroutine counts
|
||||
total int // total goroutines
|
||||
condwait map[*sync.Cond]int // blocked in sync.Cond.Wait
|
||||
blocked []*testBlockedGoroutine // otherwise blocked
|
||||
|
||||
// fake time
|
||||
now time.Time
|
||||
timers []*fakeTimer
|
||||
|
||||
// Transport testing: Report various events.
|
||||
newclientconn func(*ClientConn)
|
||||
newstream func(*clientStream)
|
||||
}
|
||||
|
||||
// testBlockedGoroutine is a blocked goroutine.
|
||||
type testBlockedGoroutine struct {
|
||||
f func() bool // blocked until f returns true
|
||||
ch chan struct{} // closed when unblocked
|
||||
}
|
||||
|
||||
func newTestSyncHooks() *testSyncHooks {
|
||||
h := &testSyncHooks{
|
||||
active: make(chan struct{}, 1),
|
||||
inactive: make(chan struct{}, 1),
|
||||
condwait: map[*sync.Cond]int{},
|
||||
}
|
||||
h.inactive <- struct{}{}
|
||||
h.now = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
return h
|
||||
}
|
||||
|
||||
// lock acquires the testSyncHooks mutex.
|
||||
func (h *testSyncHooks) lock() {
|
||||
select {
|
||||
case <-h.active:
|
||||
case <-h.inactive:
|
||||
}
|
||||
}
|
||||
|
||||
// waitInactive waits for all goroutines to become inactive.
|
||||
func (h *testSyncHooks) waitInactive() {
|
||||
for {
|
||||
<-h.inactive
|
||||
if !h.unlock() {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unlock releases the testSyncHooks mutex.
|
||||
// It reports whether any goroutines are active.
|
||||
func (h *testSyncHooks) unlock() (active bool) {
|
||||
// Look for a blocked goroutine which can be unblocked.
|
||||
blocked := h.blocked[:0]
|
||||
unblocked := false
|
||||
for _, b := range h.blocked {
|
||||
if !unblocked && b.f() {
|
||||
unblocked = true
|
||||
close(b.ch)
|
||||
} else {
|
||||
blocked = append(blocked, b)
|
||||
}
|
||||
}
|
||||
h.blocked = blocked
|
||||
|
||||
// Count goroutines blocked on condition variables.
|
||||
condwait := 0
|
||||
for _, count := range h.condwait {
|
||||
condwait += count
|
||||
}
|
||||
|
||||
if h.total > condwait+len(blocked) {
|
||||
h.active <- struct{}{}
|
||||
return true
|
||||
} else {
|
||||
h.inactive <- struct{}{}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// goRun starts a new goroutine.
|
||||
func (h *testSyncHooks) goRun(f func()) {
|
||||
h.lock()
|
||||
h.total++
|
||||
h.unlock()
|
||||
go func() {
|
||||
defer func() {
|
||||
h.lock()
|
||||
h.total--
|
||||
h.unlock()
|
||||
}()
|
||||
f()
|
||||
}()
|
||||
}
|
||||
|
||||
// blockUntil indicates that a goroutine is blocked waiting for some condition to become true.
|
||||
// It waits until f returns true before proceeding.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// h.blockUntil(func() bool {
|
||||
// // Is the context done yet?
|
||||
// select {
|
||||
// case <-ctx.Done():
|
||||
// default:
|
||||
// return false
|
||||
// }
|
||||
// return true
|
||||
// })
|
||||
// // Wait for the context to become done.
|
||||
// <-ctx.Done()
|
||||
//
|
||||
// The function f passed to blockUntil must be non-blocking and idempotent.
|
||||
func (h *testSyncHooks) blockUntil(f func() bool) {
|
||||
if f() {
|
||||
return
|
||||
}
|
||||
ch := make(chan struct{})
|
||||
h.lock()
|
||||
h.blocked = append(h.blocked, &testBlockedGoroutine{
|
||||
f: f,
|
||||
ch: ch,
|
||||
})
|
||||
h.unlock()
|
||||
<-ch
|
||||
}
|
||||
|
||||
// broadcast is sync.Cond.Broadcast.
|
||||
func (h *testSyncHooks) condBroadcast(cond *sync.Cond) {
|
||||
h.lock()
|
||||
delete(h.condwait, cond)
|
||||
h.unlock()
|
||||
cond.Broadcast()
|
||||
}
|
||||
|
||||
// broadcast is sync.Cond.Wait.
|
||||
func (h *testSyncHooks) condWait(cond *sync.Cond) {
|
||||
h.lock()
|
||||
h.condwait[cond]++
|
||||
h.unlock()
|
||||
}
|
||||
|
||||
// newTimer creates a new fake timer.
|
||||
func (h *testSyncHooks) newTimer(d time.Duration) timer {
|
||||
h.lock()
|
||||
defer h.unlock()
|
||||
t := &fakeTimer{
|
||||
hooks: h,
|
||||
when: h.now.Add(d),
|
||||
c: make(chan time.Time),
|
||||
}
|
||||
h.timers = append(h.timers, t)
|
||||
return t
|
||||
}
|
||||
|
||||
// afterFunc creates a new fake AfterFunc timer.
|
||||
func (h *testSyncHooks) afterFunc(d time.Duration, f func()) timer {
|
||||
h.lock()
|
||||
defer h.unlock()
|
||||
t := &fakeTimer{
|
||||
hooks: h,
|
||||
when: h.now.Add(d),
|
||||
f: f,
|
||||
}
|
||||
h.timers = append(h.timers, t)
|
||||
return t
|
||||
}
|
||||
|
||||
func (h *testSyncHooks) contextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
t := h.afterFunc(d, cancel)
|
||||
return ctx, func() {
|
||||
t.Stop()
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *testSyncHooks) timeUntilEvent() time.Duration {
|
||||
h.lock()
|
||||
defer h.unlock()
|
||||
var next time.Time
|
||||
for _, t := range h.timers {
|
||||
if next.IsZero() || t.when.Before(next) {
|
||||
next = t.when
|
||||
}
|
||||
}
|
||||
if d := next.Sub(h.now); d > 0 {
|
||||
return d
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// advance advances time and causes synthetic timers to fire.
|
||||
func (h *testSyncHooks) advance(d time.Duration) {
|
||||
h.lock()
|
||||
defer h.unlock()
|
||||
h.now = h.now.Add(d)
|
||||
timers := h.timers[:0]
|
||||
for _, t := range h.timers {
|
||||
t := t // remove after go.mod depends on go1.22
|
||||
t.mu.Lock()
|
||||
switch {
|
||||
case t.when.After(h.now):
|
||||
timers = append(timers, t)
|
||||
case t.when.IsZero():
|
||||
// stopped timer
|
||||
default:
|
||||
t.when = time.Time{}
|
||||
if t.c != nil {
|
||||
close(t.c)
|
||||
}
|
||||
if t.f != nil {
|
||||
h.total++
|
||||
go func() {
|
||||
defer func() {
|
||||
h.lock()
|
||||
h.total--
|
||||
h.unlock()
|
||||
}()
|
||||
t.f()
|
||||
}()
|
||||
}
|
||||
}
|
||||
t.mu.Unlock()
|
||||
}
|
||||
h.timers = timers
|
||||
}
|
||||
|
||||
// A timer wraps a time.Timer, or a synthetic equivalent in tests.
|
||||
// Unlike time.Timer, timer is single-use: The timer channel is closed when the timer expires.
|
||||
type timer interface {
|
||||
C() <-chan time.Time
|
||||
Stop() bool
|
||||
Reset(d time.Duration) bool
|
||||
}
|
||||
|
||||
// timeTimer implements timer using real time.
|
||||
type timeTimer struct {
|
||||
t *time.Timer
|
||||
c chan time.Time
|
||||
}
|
||||
|
||||
// newTimeTimer creates a new timer using real time.
|
||||
func newTimeTimer(d time.Duration) timer {
|
||||
ch := make(chan time.Time)
|
||||
t := time.AfterFunc(d, func() {
|
||||
close(ch)
|
||||
})
|
||||
return &timeTimer{t, ch}
|
||||
}
|
||||
|
||||
// newTimeAfterFunc creates an AfterFunc timer using real time.
|
||||
func newTimeAfterFunc(d time.Duration, f func()) timer {
|
||||
return &timeTimer{
|
||||
t: time.AfterFunc(d, f),
|
||||
}
|
||||
}
|
||||
|
||||
func (t timeTimer) C() <-chan time.Time { return t.c }
|
||||
func (t timeTimer) Stop() bool { return t.t.Stop() }
|
||||
func (t timeTimer) Reset(d time.Duration) bool { return t.t.Reset(d) }
|
||||
|
||||
// fakeTimer implements timer using fake time.
|
||||
type fakeTimer struct {
|
||||
hooks *testSyncHooks
|
||||
|
||||
mu sync.Mutex
|
||||
when time.Time // when the timer will fire
|
||||
c chan time.Time // closed when the timer fires; mutually exclusive with f
|
||||
f func() // called when the timer fires; mutually exclusive with c
|
||||
}
|
||||
|
||||
func (t *fakeTimer) C() <-chan time.Time { return t.c }
|
||||
|
||||
func (t *fakeTimer) Stop() bool {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
stopped := t.when.IsZero()
|
||||
t.when = time.Time{}
|
||||
return stopped
|
||||
}
|
||||
|
||||
func (t *fakeTimer) Reset(d time.Duration) bool {
|
||||
if t.c != nil || t.f == nil {
|
||||
panic("fakeTimer only supports Reset on AfterFunc timers")
|
||||
}
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.hooks.lock()
|
||||
defer t.hooks.unlock()
|
||||
active := !t.when.IsZero()
|
||||
t.when = t.hooks.now.Add(d)
|
||||
if !active {
|
||||
t.hooks.timers = append(t.hooks.timers, t)
|
||||
}
|
||||
return active
|
||||
}
|
307
vendor/golang.org/x/net/http2/transport.go
generated
vendored
307
vendor/golang.org/x/net/http2/transport.go
generated
vendored
|
@ -147,6 +147,12 @@ type Transport struct {
|
|||
// waiting for their turn.
|
||||
StrictMaxConcurrentStreams bool
|
||||
|
||||
// IdleConnTimeout is the maximum amount of time an idle
|
||||
// (keep-alive) connection will remain idle before closing
|
||||
// itself.
|
||||
// Zero means no limit.
|
||||
IdleConnTimeout time.Duration
|
||||
|
||||
// ReadIdleTimeout is the timeout after which a health check using ping
|
||||
// frame will be carried out if no frame is received on the connection.
|
||||
// Note that a ping response will is considered a received frame, so if
|
||||
|
@ -178,6 +184,8 @@ type Transport struct {
|
|||
|
||||
connPoolOnce sync.Once
|
||||
connPoolOrDef ClientConnPool // non-nil version of ConnPool
|
||||
|
||||
syncHooks *testSyncHooks
|
||||
}
|
||||
|
||||
func (t *Transport) maxHeaderListSize() uint32 {
|
||||
|
@ -302,7 +310,7 @@ type ClientConn struct {
|
|||
readerErr error // set before readerDone is closed
|
||||
|
||||
idleTimeout time.Duration // or 0 for never
|
||||
idleTimer *time.Timer
|
||||
idleTimer timer
|
||||
|
||||
mu sync.Mutex // guards following
|
||||
cond *sync.Cond // hold mu; broadcast on flow/closed changes
|
||||
|
@ -344,6 +352,60 @@ type ClientConn struct {
|
|||
werr error // first write error that has occurred
|
||||
hbuf bytes.Buffer // HPACK encoder writes into this
|
||||
henc *hpack.Encoder
|
||||
|
||||
syncHooks *testSyncHooks // can be nil
|
||||
}
|
||||
|
||||
// Hook points used for testing.
|
||||
// Outside of tests, cc.syncHooks is nil and these all have minimal implementations.
|
||||
// Inside tests, see the testSyncHooks function docs.
|
||||
|
||||
// goRun starts a new goroutine.
|
||||
func (cc *ClientConn) goRun(f func()) {
|
||||
if cc.syncHooks != nil {
|
||||
cc.syncHooks.goRun(f)
|
||||
return
|
||||
}
|
||||
go f()
|
||||
}
|
||||
|
||||
// condBroadcast is cc.cond.Broadcast.
|
||||
func (cc *ClientConn) condBroadcast() {
|
||||
if cc.syncHooks != nil {
|
||||
cc.syncHooks.condBroadcast(cc.cond)
|
||||
}
|
||||
cc.cond.Broadcast()
|
||||
}
|
||||
|
||||
// condWait is cc.cond.Wait.
|
||||
func (cc *ClientConn) condWait() {
|
||||
if cc.syncHooks != nil {
|
||||
cc.syncHooks.condWait(cc.cond)
|
||||
}
|
||||
cc.cond.Wait()
|
||||
}
|
||||
|
||||
// newTimer creates a new time.Timer, or a synthetic timer in tests.
|
||||
func (cc *ClientConn) newTimer(d time.Duration) timer {
|
||||
if cc.syncHooks != nil {
|
||||
return cc.syncHooks.newTimer(d)
|
||||
}
|
||||
return newTimeTimer(d)
|
||||
}
|
||||
|
||||
// afterFunc creates a new time.AfterFunc timer, or a synthetic timer in tests.
|
||||
func (cc *ClientConn) afterFunc(d time.Duration, f func()) timer {
|
||||
if cc.syncHooks != nil {
|
||||
return cc.syncHooks.afterFunc(d, f)
|
||||
}
|
||||
return newTimeAfterFunc(d, f)
|
||||
}
|
||||
|
||||
func (cc *ClientConn) contextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) {
|
||||
if cc.syncHooks != nil {
|
||||
return cc.syncHooks.contextWithTimeout(ctx, d)
|
||||
}
|
||||
return context.WithTimeout(ctx, d)
|
||||
}
|
||||
|
||||
// clientStream is the state for a single HTTP/2 stream. One of these
|
||||
|
@ -425,7 +487,7 @@ func (cs *clientStream) abortStreamLocked(err error) {
|
|||
// TODO(dneil): Clean up tests where cs.cc.cond is nil.
|
||||
if cs.cc.cond != nil {
|
||||
// Wake up writeRequestBody if it is waiting on flow control.
|
||||
cs.cc.cond.Broadcast()
|
||||
cs.cc.condBroadcast()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -435,7 +497,7 @@ func (cs *clientStream) abortRequestBodyWrite() {
|
|||
defer cc.mu.Unlock()
|
||||
if cs.reqBody != nil && cs.reqBodyClosed == nil {
|
||||
cs.closeReqBodyLocked()
|
||||
cc.cond.Broadcast()
|
||||
cc.condBroadcast()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -445,10 +507,10 @@ func (cs *clientStream) closeReqBodyLocked() {
|
|||
}
|
||||
cs.reqBodyClosed = make(chan struct{})
|
||||
reqBodyClosed := cs.reqBodyClosed
|
||||
go func() {
|
||||
cs.cc.goRun(func() {
|
||||
cs.reqBody.Close()
|
||||
close(reqBodyClosed)
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
type stickyErrWriter struct {
|
||||
|
@ -537,15 +599,6 @@ func authorityAddr(scheme string, authority string) (addr string) {
|
|||
return net.JoinHostPort(host, port)
|
||||
}
|
||||
|
||||
var retryBackoffHook func(time.Duration) *time.Timer
|
||||
|
||||
func backoffNewTimer(d time.Duration) *time.Timer {
|
||||
if retryBackoffHook != nil {
|
||||
return retryBackoffHook(d)
|
||||
}
|
||||
return time.NewTimer(d)
|
||||
}
|
||||
|
||||
// RoundTripOpt is like RoundTrip, but takes options.
|
||||
func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
||||
if !(req.URL.Scheme == "https" || (req.URL.Scheme == "http" && t.AllowHTTP)) {
|
||||
|
@ -573,13 +626,27 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
|
|||
backoff := float64(uint(1) << (uint(retry) - 1))
|
||||
backoff += backoff * (0.1 * mathrand.Float64())
|
||||
d := time.Second * time.Duration(backoff)
|
||||
timer := backoffNewTimer(d)
|
||||
var tm timer
|
||||
if t.syncHooks != nil {
|
||||
tm = t.syncHooks.newTimer(d)
|
||||
t.syncHooks.blockUntil(func() bool {
|
||||
select {
|
||||
case <-tm.C():
|
||||
case <-req.Context().Done():
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
tm = newTimeTimer(d)
|
||||
}
|
||||
select {
|
||||
case <-timer.C:
|
||||
case <-tm.C():
|
||||
t.vlogf("RoundTrip retrying after failure: %v", roundTripErr)
|
||||
continue
|
||||
case <-req.Context().Done():
|
||||
timer.Stop()
|
||||
tm.Stop()
|
||||
err = req.Context().Err()
|
||||
}
|
||||
}
|
||||
|
@ -658,6 +725,9 @@ func canRetryError(err error) bool {
|
|||
}
|
||||
|
||||
func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*ClientConn, error) {
|
||||
if t.syncHooks != nil {
|
||||
return t.newClientConn(nil, singleUse, t.syncHooks)
|
||||
}
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -666,7 +736,7 @@ func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse b
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t.newClientConn(tconn, singleUse)
|
||||
return t.newClientConn(tconn, singleUse, nil)
|
||||
}
|
||||
|
||||
func (t *Transport) newTLSConfig(host string) *tls.Config {
|
||||
|
@ -732,10 +802,10 @@ func (t *Transport) maxEncoderHeaderTableSize() uint32 {
|
|||
}
|
||||
|
||||
func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
|
||||
return t.newClientConn(c, t.disableKeepAlives())
|
||||
return t.newClientConn(c, t.disableKeepAlives(), nil)
|
||||
}
|
||||
|
||||
func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, error) {
|
||||
func (t *Transport) newClientConn(c net.Conn, singleUse bool, hooks *testSyncHooks) (*ClientConn, error) {
|
||||
cc := &ClientConn{
|
||||
t: t,
|
||||
tconn: c,
|
||||
|
@ -750,10 +820,15 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
|
|||
wantSettingsAck: true,
|
||||
pings: make(map[[8]byte]chan struct{}),
|
||||
reqHeaderMu: make(chan struct{}, 1),
|
||||
syncHooks: hooks,
|
||||
}
|
||||
if hooks != nil {
|
||||
hooks.newclientconn(cc)
|
||||
c = cc.tconn
|
||||
}
|
||||
if d := t.idleConnTimeout(); d != 0 {
|
||||
cc.idleTimeout = d
|
||||
cc.idleTimer = time.AfterFunc(d, cc.onIdleTimeout)
|
||||
cc.idleTimer = cc.afterFunc(d, cc.onIdleTimeout)
|
||||
}
|
||||
if VerboseLogs {
|
||||
t.vlogf("http2: Transport creating client conn %p to %v", cc, c.RemoteAddr())
|
||||
|
@ -818,7 +893,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
|
|||
return nil, cc.werr
|
||||
}
|
||||
|
||||
go cc.readLoop()
|
||||
cc.goRun(cc.readLoop)
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
|
@ -826,7 +901,7 @@ func (cc *ClientConn) healthCheck() {
|
|||
pingTimeout := cc.t.pingTimeout()
|
||||
// We don't need to periodically ping in the health check, because the readLoop of ClientConn will
|
||||
// trigger the healthCheck again if there is no frame received.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), pingTimeout)
|
||||
ctx, cancel := cc.contextWithTimeout(context.Background(), pingTimeout)
|
||||
defer cancel()
|
||||
cc.vlogf("http2: Transport sending health check")
|
||||
err := cc.Ping(ctx)
|
||||
|
@ -1056,7 +1131,7 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
|
|||
// Wait for all in-flight streams to complete or connection to close
|
||||
done := make(chan struct{})
|
||||
cancelled := false // guarded by cc.mu
|
||||
go func() {
|
||||
cc.goRun(func() {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
for {
|
||||
|
@ -1068,9 +1143,9 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
|
|||
if cancelled {
|
||||
break
|
||||
}
|
||||
cc.cond.Wait()
|
||||
cc.condWait()
|
||||
}
|
||||
}()
|
||||
})
|
||||
shutdownEnterWaitStateHook()
|
||||
select {
|
||||
case <-done:
|
||||
|
@ -1080,7 +1155,7 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
|
|||
cc.mu.Lock()
|
||||
// Free the goroutine above
|
||||
cancelled = true
|
||||
cc.cond.Broadcast()
|
||||
cc.condBroadcast()
|
||||
cc.mu.Unlock()
|
||||
return ctx.Err()
|
||||
}
|
||||
|
@ -1118,7 +1193,7 @@ func (cc *ClientConn) closeForError(err error) {
|
|||
for _, cs := range cc.streams {
|
||||
cs.abortStreamLocked(err)
|
||||
}
|
||||
cc.cond.Broadcast()
|
||||
cc.condBroadcast()
|
||||
cc.mu.Unlock()
|
||||
cc.closeConn()
|
||||
}
|
||||
|
@ -1215,6 +1290,10 @@ func (cc *ClientConn) decrStreamReservationsLocked() {
|
|||
}
|
||||
|
||||
func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return cc.roundTrip(req, nil)
|
||||
}
|
||||
|
||||
func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream)) (*http.Response, error) {
|
||||
ctx := req.Context()
|
||||
cs := &clientStream{
|
||||
cc: cc,
|
||||
|
@ -1229,9 +1308,23 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||
respHeaderRecv: make(chan struct{}),
|
||||
donec: make(chan struct{}),
|
||||
}
|
||||
go cs.doRequest(req)
|
||||
cc.goRun(func() {
|
||||
cs.doRequest(req)
|
||||
})
|
||||
|
||||
waitDone := func() error {
|
||||
if cc.syncHooks != nil {
|
||||
cc.syncHooks.blockUntil(func() bool {
|
||||
select {
|
||||
case <-cs.donec:
|
||||
case <-ctx.Done():
|
||||
case <-cs.reqCancel:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
select {
|
||||
case <-cs.donec:
|
||||
return nil
|
||||
|
@ -1292,7 +1385,24 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||
return err
|
||||
}
|
||||
|
||||
if streamf != nil {
|
||||
streamf(cs)
|
||||
}
|
||||
|
||||
for {
|
||||
if cc.syncHooks != nil {
|
||||
cc.syncHooks.blockUntil(func() bool {
|
||||
select {
|
||||
case <-cs.respHeaderRecv:
|
||||
case <-cs.abort:
|
||||
case <-ctx.Done():
|
||||
case <-cs.reqCancel:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
select {
|
||||
case <-cs.respHeaderRecv:
|
||||
return handleResponseHeaders()
|
||||
|
@ -1348,6 +1458,21 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
|
|||
if cc.reqHeaderMu == nil {
|
||||
panic("RoundTrip on uninitialized ClientConn") // for tests
|
||||
}
|
||||
var newStreamHook func(*clientStream)
|
||||
if cc.syncHooks != nil {
|
||||
newStreamHook = cc.syncHooks.newstream
|
||||
cc.syncHooks.blockUntil(func() bool {
|
||||
select {
|
||||
case cc.reqHeaderMu <- struct{}{}:
|
||||
<-cc.reqHeaderMu
|
||||
case <-cs.reqCancel:
|
||||
case <-ctx.Done():
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
select {
|
||||
case cc.reqHeaderMu <- struct{}{}:
|
||||
case <-cs.reqCancel:
|
||||
|
@ -1372,6 +1497,10 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
|
|||
}
|
||||
cc.mu.Unlock()
|
||||
|
||||
if newStreamHook != nil {
|
||||
newStreamHook(cs)
|
||||
}
|
||||
|
||||
// TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
|
||||
if !cc.t.disableCompression() &&
|
||||
req.Header.Get("Accept-Encoding") == "" &&
|
||||
|
@ -1452,15 +1581,30 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
|
|||
var respHeaderTimer <-chan time.Time
|
||||
var respHeaderRecv chan struct{}
|
||||
if d := cc.responseHeaderTimeout(); d != 0 {
|
||||
timer := time.NewTimer(d)
|
||||
timer := cc.newTimer(d)
|
||||
defer timer.Stop()
|
||||
respHeaderTimer = timer.C
|
||||
respHeaderTimer = timer.C()
|
||||
respHeaderRecv = cs.respHeaderRecv
|
||||
}
|
||||
// Wait until the peer half-closes its end of the stream,
|
||||
// or until the request is aborted (via context, error, or otherwise),
|
||||
// whichever comes first.
|
||||
for {
|
||||
if cc.syncHooks != nil {
|
||||
cc.syncHooks.blockUntil(func() bool {
|
||||
select {
|
||||
case <-cs.peerClosed:
|
||||
case <-respHeaderTimer:
|
||||
case <-respHeaderRecv:
|
||||
case <-cs.abort:
|
||||
case <-ctx.Done():
|
||||
case <-cs.reqCancel:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
select {
|
||||
case <-cs.peerClosed:
|
||||
return nil
|
||||
|
@ -1609,7 +1753,7 @@ func (cc *ClientConn) awaitOpenSlotForStreamLocked(cs *clientStream) error {
|
|||
return nil
|
||||
}
|
||||
cc.pendingRequests++
|
||||
cc.cond.Wait()
|
||||
cc.condWait()
|
||||
cc.pendingRequests--
|
||||
select {
|
||||
case <-cs.abort:
|
||||
|
@ -1871,10 +2015,26 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error)
|
|||
cs.flow.take(take)
|
||||
return take, nil
|
||||
}
|
||||
cc.cond.Wait()
|
||||
cc.condWait()
|
||||
}
|
||||
}
|
||||
|
||||
func validateHeaders(hdrs http.Header) string {
|
||||
for k, vv := range hdrs {
|
||||
if !httpguts.ValidHeaderFieldName(k) {
|
||||
return fmt.Sprintf("name %q", k)
|
||||
}
|
||||
for _, v := range vv {
|
||||
if !httpguts.ValidHeaderFieldValue(v) {
|
||||
// Don't include the value in the error,
|
||||
// because it may be sensitive.
|
||||
return fmt.Sprintf("value for header %q", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var errNilRequestURL = errors.New("http2: Request.URI is nil")
|
||||
|
||||
// requires cc.wmu be held.
|
||||
|
@ -1912,19 +2072,14 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
|
|||
}
|
||||
}
|
||||
|
||||
// Check for any invalid headers and return an error before we
|
||||
// Check for any invalid headers+trailers and return an error before we
|
||||
// potentially pollute our hpack state. (We want to be able to
|
||||
// continue to reuse the hpack encoder for future requests)
|
||||
for k, vv := range req.Header {
|
||||
if !httpguts.ValidHeaderFieldName(k) {
|
||||
return nil, fmt.Errorf("invalid HTTP header name %q", k)
|
||||
}
|
||||
for _, v := range vv {
|
||||
if !httpguts.ValidHeaderFieldValue(v) {
|
||||
// Don't include the value in the error, because it may be sensitive.
|
||||
return nil, fmt.Errorf("invalid HTTP header value for header %q", k)
|
||||
}
|
||||
}
|
||||
if err := validateHeaders(req.Header); err != "" {
|
||||
return nil, fmt.Errorf("invalid HTTP header %s", err)
|
||||
}
|
||||
if err := validateHeaders(req.Trailer); err != "" {
|
||||
return nil, fmt.Errorf("invalid HTTP trailer %s", err)
|
||||
}
|
||||
|
||||
enumerateHeaders := func(f func(name, value string)) {
|
||||
|
@ -2143,7 +2298,7 @@ func (cc *ClientConn) forgetStreamID(id uint32) {
|
|||
}
|
||||
// Wake up writeRequestBody via clientStream.awaitFlowControl and
|
||||
// wake up RoundTrip if there is a pending request.
|
||||
cc.cond.Broadcast()
|
||||
cc.condBroadcast()
|
||||
|
||||
closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives() || cc.goAway != nil
|
||||
if closeOnIdle && cc.streamsReserved == 0 && len(cc.streams) == 0 {
|
||||
|
@ -2231,7 +2386,7 @@ func (rl *clientConnReadLoop) cleanup() {
|
|||
cs.abortStreamLocked(err)
|
||||
}
|
||||
}
|
||||
cc.cond.Broadcast()
|
||||
cc.condBroadcast()
|
||||
cc.mu.Unlock()
|
||||
}
|
||||
|
||||
|
@ -2266,10 +2421,9 @@ func (rl *clientConnReadLoop) run() error {
|
|||
cc := rl.cc
|
||||
gotSettings := false
|
||||
readIdleTimeout := cc.t.ReadIdleTimeout
|
||||
var t *time.Timer
|
||||
var t timer
|
||||
if readIdleTimeout != 0 {
|
||||
t = time.AfterFunc(readIdleTimeout, cc.healthCheck)
|
||||
defer t.Stop()
|
||||
t = cc.afterFunc(readIdleTimeout, cc.healthCheck)
|
||||
}
|
||||
for {
|
||||
f, err := cc.fr.ReadFrame()
|
||||
|
@ -2684,7 +2838,7 @@ func (rl *clientConnReadLoop) processData(f *DataFrame) error {
|
|||
})
|
||||
return nil
|
||||
}
|
||||
if !cs.firstByte {
|
||||
if !cs.pastHeaders {
|
||||
cc.logf("protocol error: received DATA before a HEADERS frame")
|
||||
rl.endStreamError(cs, StreamError{
|
||||
StreamID: f.StreamID,
|
||||
|
@ -2867,7 +3021,7 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
|
|||
for _, cs := range cc.streams {
|
||||
cs.flow.add(delta)
|
||||
}
|
||||
cc.cond.Broadcast()
|
||||
cc.condBroadcast()
|
||||
|
||||
cc.initialWindowSize = s.Val
|
||||
case SettingHeaderTableSize:
|
||||
|
@ -2911,9 +3065,18 @@ func (rl *clientConnReadLoop) processWindowUpdate(f *WindowUpdateFrame) error {
|
|||
fl = &cs.flow
|
||||
}
|
||||
if !fl.add(int32(f.Increment)) {
|
||||
// For stream, the sender sends RST_STREAM with an error code of FLOW_CONTROL_ERROR
|
||||
if cs != nil {
|
||||
rl.endStreamError(cs, StreamError{
|
||||
StreamID: f.StreamID,
|
||||
Code: ErrCodeFlowControl,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
return ConnectionError(ErrCodeFlowControl)
|
||||
}
|
||||
cc.cond.Broadcast()
|
||||
cc.condBroadcast()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -2955,24 +3118,38 @@ func (cc *ClientConn) Ping(ctx context.Context) error {
|
|||
}
|
||||
cc.mu.Unlock()
|
||||
}
|
||||
errc := make(chan error, 1)
|
||||
go func() {
|
||||
var pingError error
|
||||
errc := make(chan struct{})
|
||||
cc.goRun(func() {
|
||||
cc.wmu.Lock()
|
||||
defer cc.wmu.Unlock()
|
||||
if err := cc.fr.WritePing(false, p); err != nil {
|
||||
errc <- err
|
||||
if pingError = cc.fr.WritePing(false, p); pingError != nil {
|
||||
close(errc)
|
||||
return
|
||||
}
|
||||
if err := cc.bw.Flush(); err != nil {
|
||||
errc <- err
|
||||
if pingError = cc.bw.Flush(); pingError != nil {
|
||||
close(errc)
|
||||
return
|
||||
}
|
||||
}()
|
||||
})
|
||||
if cc.syncHooks != nil {
|
||||
cc.syncHooks.blockUntil(func() bool {
|
||||
select {
|
||||
case <-c:
|
||||
case <-errc:
|
||||
case <-ctx.Done():
|
||||
case <-cc.readerDone:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
select {
|
||||
case <-c:
|
||||
return nil
|
||||
case err := <-errc:
|
||||
return err
|
||||
case <-errc:
|
||||
return pingError
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-cc.readerDone:
|
||||
|
@ -3141,9 +3318,17 @@ func (rt noDialH2RoundTripper) RoundTrip(req *http.Request) (*http.Response, err
|
|||
}
|
||||
|
||||
func (t *Transport) idleConnTimeout() time.Duration {
|
||||
// to keep things backwards compatible, we use non-zero values of
|
||||
// IdleConnTimeout, followed by using the IdleConnTimeout on the underlying
|
||||
// http1 transport, followed by 0
|
||||
if t.IdleConnTimeout != 0 {
|
||||
return t.IdleConnTimeout
|
||||
}
|
||||
|
||||
if t.t1 != nil {
|
||||
return t.t1.IdleConnTimeout
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
59
vendor/golang.org/x/net/websocket/client.go
generated
vendored
59
vendor/golang.org/x/net/websocket/client.go
generated
vendored
|
@ -6,10 +6,12 @@ package websocket
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DialError is an error that occurs while dialling a websocket server.
|
||||
|
@ -79,28 +81,59 @@ func parseAuthority(location *url.URL) string {
|
|||
|
||||
// DialConfig opens a new client connection to a WebSocket with a config.
|
||||
func DialConfig(config *Config) (ws *Conn, err error) {
|
||||
var client net.Conn
|
||||
return config.DialContext(context.Background())
|
||||
}
|
||||
|
||||
// DialContext opens a new client connection to a WebSocket, with context support for timeouts/cancellation.
|
||||
func (config *Config) DialContext(ctx context.Context) (*Conn, error) {
|
||||
if config.Location == nil {
|
||||
return nil, &DialError{config, ErrBadWebSocketLocation}
|
||||
}
|
||||
if config.Origin == nil {
|
||||
return nil, &DialError{config, ErrBadWebSocketOrigin}
|
||||
}
|
||||
|
||||
dialer := config.Dialer
|
||||
if dialer == nil {
|
||||
dialer = &net.Dialer{}
|
||||
}
|
||||
client, err = dialWithDialer(dialer, config)
|
||||
if err != nil {
|
||||
goto Error
|
||||
}
|
||||
ws, err = NewClient(config, client)
|
||||
if err != nil {
|
||||
client.Close()
|
||||
goto Error
|
||||
}
|
||||
return
|
||||
|
||||
Error:
|
||||
return nil, &DialError{config, err}
|
||||
client, err := dialWithDialer(ctx, dialer, config)
|
||||
if err != nil {
|
||||
return nil, &DialError{config, err}
|
||||
}
|
||||
|
||||
// Cleanup the connection if we fail to create the websocket successfully
|
||||
success := false
|
||||
defer func() {
|
||||
if !success {
|
||||
_ = client.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
var ws *Conn
|
||||
var wsErr error
|
||||
doneConnecting := make(chan struct{})
|
||||
go func() {
|
||||
defer close(doneConnecting)
|
||||
ws, err = NewClient(config, client)
|
||||
if err != nil {
|
||||
wsErr = &DialError{config, err}
|
||||
}
|
||||
}()
|
||||
|
||||
// The websocket.NewClient() function can block indefinitely, make sure that we
|
||||
// respect the deadlines specified by the context.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Force the pending operations to fail, terminating the pending connection attempt
|
||||
_ = client.SetDeadline(time.Now())
|
||||
<-doneConnecting // Wait for the goroutine that tries to establish the connection to finish
|
||||
return nil, &DialError{config, ctx.Err()}
|
||||
case <-doneConnecting:
|
||||
if wsErr == nil {
|
||||
success = true // Disarm the deferred connection cleanup
|
||||
}
|
||||
return ws, wsErr
|
||||
}
|
||||
}
|
||||
|
|
11
vendor/golang.org/x/net/websocket/dial.go
generated
vendored
11
vendor/golang.org/x/net/websocket/dial.go
generated
vendored
|
@ -5,18 +5,23 @@
|
|||
package websocket
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
)
|
||||
|
||||
func dialWithDialer(dialer *net.Dialer, config *Config) (conn net.Conn, err error) {
|
||||
func dialWithDialer(ctx context.Context, dialer *net.Dialer, config *Config) (conn net.Conn, err error) {
|
||||
switch config.Location.Scheme {
|
||||
case "ws":
|
||||
conn, err = dialer.Dial("tcp", parseAuthority(config.Location))
|
||||
conn, err = dialer.DialContext(ctx, "tcp", parseAuthority(config.Location))
|
||||
|
||||
case "wss":
|
||||
conn, err = tls.DialWithDialer(dialer, "tcp", parseAuthority(config.Location), config.TlsConfig)
|
||||
tlsDialer := &tls.Dialer{
|
||||
NetDialer: dialer,
|
||||
Config: config.TlsConfig,
|
||||
}
|
||||
|
||||
conn, err = tlsDialer.DialContext(ctx, "tcp", parseAuthority(config.Location))
|
||||
default:
|
||||
err = ErrBadScheme
|
||||
}
|
||||
|
|
6
vendor/modules.txt
vendored
6
vendor/modules.txt
vendored
|
@ -256,7 +256,7 @@ github.com/containerd/cgroups/v3/cgroup2/stats
|
|||
# github.com/containerd/console v1.0.4
|
||||
## explicit; go 1.13
|
||||
github.com/containerd/console
|
||||
# github.com/containerd/containerd v1.7.14
|
||||
# github.com/containerd/containerd v1.7.15
|
||||
## explicit; go 1.21
|
||||
github.com/containerd/containerd
|
||||
github.com/containerd/containerd/api/events
|
||||
|
@ -1279,7 +1279,7 @@ go.uber.org/zap/internal/bufferpool
|
|||
go.uber.org/zap/internal/color
|
||||
go.uber.org/zap/internal/exit
|
||||
go.uber.org/zap/zapcore
|
||||
# golang.org/x/crypto v0.17.0
|
||||
# golang.org/x/crypto v0.21.0
|
||||
## explicit; go 1.18
|
||||
golang.org/x/crypto/blowfish
|
||||
golang.org/x/crypto/chacha20
|
||||
|
@ -1311,7 +1311,7 @@ golang.org/x/exp/slices
|
|||
golang.org/x/mod/internal/lazyregexp
|
||||
golang.org/x/mod/module
|
||||
golang.org/x/mod/semver
|
||||
# golang.org/x/net v0.18.0
|
||||
# golang.org/x/net v0.23.0
|
||||
## explicit; go 1.18
|
||||
golang.org/x/net/bpf
|
||||
golang.org/x/net/context
|
||||
|
|
Loading…
Reference in a new issue