Compare commits

...

69 commits
26.0 ... master

Author SHA1 Message Date
Bjorn Neergaard
801fd16e3e
Merge pull request #47735 from cpuguy83/better_walk_error
Include more details in errNotManifestOrIndex
2024-04-19 18:16:12 -07:00
Brian Goff
6667e96dad Include more details in errnotManifestOrIndex
This error is returned when attempting to walk a descriptor that
*should* be an index or a manifest.
Without this the error is not very helpful sicne there's no way to tell
what triggered it.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
2024-04-19 21:13:05 +00:00
Paweł Gronowski
ee8b788538
Merge pull request #47734 from krissetto/image-history-timestamp-dereference
fix: avoid nil dereference on image history `Created` value
2024-04-19 14:23:02 +02:00
Paweł Gronowski
96c9353e9b
Merge pull request #47723 from vvoland/builder-fix-workdir-slash
builder: Fix `WORKDIR` with a trailing slash causing a cache miss
2024-04-19 13:56:40 +02:00
Christopher Petito
ab570ab3d6 nil dereference fix on image history Created value
Issue was caused by the changes here https://github.com/moby/moby/pull/45504
First released in v25.0.0-beta.1

Signed-off-by: Christopher Petito <47751006+krissetto@users.noreply.github.com>
2024-04-19 10:44:30 +00:00
Paweł Gronowski
7532420f3b
container/SetupWorkingDirectory: Don't mutate config
Don't mutate the container's `Config.WorkingDir` permanently with a
cleaned path when creating a working directory.

Move the `filepath.Clean` to the `translateWorkingDir` instead.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-04-19 12:42:20 +02:00
Paweł Gronowski
a4d5b6b4d0
builder/normalizeWorkdir: Always return cleaned path
The `normalizeWorkdir` function has two branches, one that returns a
result of `filepath.Join` which always returns a cleaned path, and
another one where the input string is returned unmodified.

To make these two outputs consistent, also clean the path in the second
branch.

This also makes the cleaning of the container workdir explicit in the
`normalizeWorkdir` function instead of relying on the
`SetupWorkingDirectory` to mutate it.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-04-19 12:42:19 +02:00
Paweł Gronowski
e829cca0ee
Merge pull request #47584 from robmry/upstream_dns_windows
Windows DNS resolver forwarding
2024-04-19 11:34:50 +02:00
Sebastiaan van Stijn
82d8f8d6e6
Merge pull request from GHSA-x84c-p2g9-rqv9
Disable IPv6 for endpoints in '--ipv6=false' networks.
2024-04-18 17:50:35 +02:00
Rob Murray
6c68be24a2 Windows DNS resolver forwarding
Make the internal DNS resolver for Windows containers forward requests
to upsteam DNS servers when it cannot respond itself, rather than
returning SERVFAIL.

Windows containers are normally configured with the internal resolver
first for service discovery (container name lookup), then external
resolvers from '--dns' or the host's networking configuration.

When a tool like ping gets a SERVFAIL from the internal resolver, it
tries the other nameservers. But, nslookup does not, and with this
change it does not need to.

The internal resolver learns external server addresses from the
container's HNSEndpoint configuration, so it will use the same DNS
servers as processes in the container.

The internal resolver for Windows containers listens on the network's
gateway address, and each container may have a different set of external
DNS servers. So, the resolver uses the source address of the DNS request
to select external resolvers.

On Windows, daemon.json feature option 'windows-no-dns-proxy' can be used
to prevent the internal resolver from forwarding requests (restoring the
old behaviour).

Signed-off-by: Rob Murray <rob.murray@docker.com>
2024-04-16 18:57:28 +01:00
Sebastiaan van Stijn
b7c059886c
Merge pull request #47706 from elezar/bump-container-device-interface
Update tags.cncf.io/container-device-interface to v0.7.1
2024-04-16 15:46:49 +02:00
Evan Lezar
745e2356ab Update tags.cncf.io/container-device-interface to v0.7.1
This also bumps the maximum supported CDI specification to v0.7.0.

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2024-04-16 12:06:23 +02:00
Sebastiaan van Stijn
29f24a828b
Merge pull request #47719 from thaJeztah/vendor_runtime_spec
vendor: github.com/opencontainers/runtime-spec v1.2.0
2024-04-16 11:50:50 +02:00
Sebastiaan van Stijn
0d6a1a212b
vendor: github.com/opencontainers/runtime-spec v1.2.0
- deprecate Prestart hook
- deprecate kernel memory limits

Additions

- config: add idmap and ridmap mount options
- config.md: allow empty mappings for [r]idmap
- features-linux: Expose idmap information
- mount: Allow relative mount destinations on Linux
- features: add potentiallyUnsafeConfigAnnotations
- config: add support for org.opencontainers.image annotations

Minor fixes:

- config: improve bind mount and propagation doc

full diff: https://github.com/opencontainers/runtime-spec/compare/v1.1.0...v1.2.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-04-15 17:56:26 +02:00
Sebastiaan van Stijn
f5209d23a8
daemon: add nolint-comments for deprecated kernel-memory options, hooks
This adds some nolint-comments for the deprecated kernel-memory options; we
deprecated these, but they could technically still be accepted by alternative
runtimes.

    daemon/daemon_unix.go:108:3: SA1019: memory.Kernel is deprecated: kernel-memory limits are not supported in cgroups v2, and were obsoleted in [kernel v5.4]. This field should no longer be used, as it may be ignored by runtimes. (staticcheck)
            memory.Kernel = &config.KernelMemory
            ^
    daemon/update_linux.go:63:3: SA1019: memory.Kernel is deprecated: kernel-memory limits are not supported in cgroups v2, and were obsoleted in [kernel v5.4]. This field should no longer be used, as it may be ignored by runtimes. (staticcheck)
            memory.Kernel = &resources.KernelMemory
            ^

Prestart hooks are deprecated, and more granular hooks should be used instead.
CreateRuntime are the closest equivalent, and executed in the same locations
as Prestart-hooks, but depending on what these hooks do, possibly one of the
other hooks could be used instead (such as CreateContainer or StartContainer).
As these hooks are still supported, this patch adds nolint comments, but adds
some TODOs to consider migrating to something else;

    daemon/nvidia_linux.go:86:2: SA1019: s.Hooks.Prestart is deprecated: use [Hooks.CreateRuntime], [Hooks.CreateContainer], and [Hooks.StartContainer] instead, which allow more granular hook control during the create and start phase. (staticcheck)
        s.Hooks.Prestart = append(s.Hooks.Prestart, specs.Hook{
        ^

    daemon/oci_linux.go:76:5: SA1019: s.Hooks.Prestart is deprecated: use [Hooks.CreateRuntime], [Hooks.CreateContainer], and [Hooks.StartContainer] instead, which allow more granular hook control during the create and start phase. (staticcheck)
                    s.Hooks.Prestart = append(s.Hooks.Prestart, specs.Hook{
                    ^

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-04-15 17:55:47 +02:00
Paweł Gronowski
442f29a699
Merge pull request #47711 from vvoland/swarm-subpath
daemon/cluster/executor: Add volume `Subpath`
2024-04-15 17:45:20 +02:00
Rob Murray
f07644e17e Add netiputil.AddrPortFromNet()
Co-authored-by: Cory Snider <csnider@mirantis.com>
Signed-off-by: Rob Murray <rob.murray@docker.com>
2024-04-15 14:51:20 +01:00
Paweł Gronowski
d3c051318f
daemon/cluster/executor: Add volume Subpath
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-04-15 14:14:32 +02:00
Paweł Gronowski
5368c3a04f
vendor: github.com/moby/swarmkit/v2 master (f3ffc0881d0e)
full diff: 911c97650f...f3ffc0881d

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-04-15 14:14:30 +02:00
Lei Jitang
8d5d655db0
Merge pull request #47708 from ViToni/fix_typos
Fix typo
2024-04-13 10:33:58 +08:00
Victor Toni
f51e18f58e Fix typo
Signed-off-by: Victor Toni <victor.toni@gmail.com>
2024-04-11 00:19:05 +02:00
Rob Murray
57dd56726a Disable IPv6 for endpoints in '--ipv6=false' networks.
No IPAM IPv6 address is given to an interface in a network with
'--ipv6=false', but the kernel would assign a link-local address and,
in a macvlan/ipvlan network, the interface may get a SLAAC-assigned
address.

So, disable IPv6 on the interface to avoid that.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2024-04-10 17:11:20 +01:00
Paweł Gronowski
f9dfd139ec
Merge pull request #47657 from siepkes/illumos_fix
Minor fix for illumos support
2024-04-10 12:35:14 +02:00
Paweł Gronowski
051d587447
Merge pull request #47677 from robmry/47662_ipvlan_l3_dns
Enable external DNS for ipvlan-l3, and disable it for macvlan/ipvlan with no parent interface
2024-04-10 12:23:40 +02:00
Rob Murray
9954d7c6bd Run ipvlan tests even if 'modprobe ipvlan' fails
This reverts commit a77e147d32.

The ipvlan integration tests have been skipped in CI because of a check
intended to ensure the kernel has ipvlan support - which failed, but
seems to be unnecessary (probably because kernels have moved on).

Signed-off-by: Rob Murray <rob.murray@docker.com>
2024-04-10 08:52:08 +01:00
Rob Murray
cd7240f6d9 Stop macvlan with no parent from using ext-dns
We document that an macvlan network with no parent interface is
equivalent to a '--internal' network. But, in this case, an macvlan
network was still configured with a gateway. So, DNS proxying would
be enabled in the internal resolver (and, if the host's resolver
was on a localhost address, requests to external resolvers from the
host's network namespace would succeed).

This change disables configuration of a gateway for a macvlan Endpoint
if no parent interface is specified.

(Note if a parent interface with no external network is supplied as
'-o parent=<dummy>', the gateway will still be set up. Documentation
will need to be updated to note that '--internal' should be used to
prevent DNS request forwarding in this case.)

Signed-off-by: Rob Murray <rob.murray@docker.com>
2024-04-10 08:51:00 +01:00
Rob Murray
17b8631545 Enable DNS proxying for ipvlan-l3
The internal DNS resolver should only forward requests to external
resolvers if the libnetwork.Sandbox served by the resolver has external
network access (so, no forwarding for '--internal' networks).

The test for external network access was whether the Sandbox had an
Endpoint with a gateway configured.

However, an ipvlan-l3 networks with external network access does not
have a gateway, it has a default route bound to an interface.

Also, we document that an ipvlan network with no parent interface is
equivalent to a '--internal' network. But, in this case, an ipvlan-l2
network was configured with a gateway. So, DNS proxying would be enabled
in the internal resolver (and, if the host's resolver was on a localhost
address, requests to external resolvers from the host's network
namespace would succeed).

So, this change adjusts the test for enabling DNS proxying to include
a check for '--internal' (as a shortcut) and, for non-internal networks,
checks for a default route as well as a gateway. It also disables
configuration of a gateway or a default route for an ipvlan Endpoint if
no parent interface is specified.

(Note if a parent interface with no external network is supplied as
'-o parent=<dummy>', the gateway/default route will still be set up
and external DNS proxying will be enabled. The network must be
configured as '--internal' to prevent that from happening.)

Signed-off-by: Rob Murray <rob.murray@docker.com>
2024-04-10 08:50:57 +01:00
Sebastiaan van Stijn
8383c487c6
Merge pull request #47691 from vvoland/vendor-master-containerd-v1.7.15
vendor: github.com/containerd/containerd v1.7.15
2024-04-09 12:08:31 +02:00
Sebastiaan van Stijn
7a54a16740
Merge pull request #47647 from vvoland/ci-backport-title
github/ci: Check if backport is opened against the expected branch
2024-04-08 19:15:37 +02:00
Sebastiaan van Stijn
2fabb28813
Merge pull request #47689 from vvoland/update-containerd
update containerd binary to v1.7.15
2024-04-08 19:07:18 +02:00
Paweł Gronowski
5ae5969739
vendor: github.com/containerd/containerd v1.7.15
full diff: https://github.com/containerd/containerd/compare/v1.7.14...v1.7.15

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-04-08 14:33:32 +02:00
Paweł Gronowski
3485cfbb1e
update containerd binary to v1.7.15
Update the containerd binary that's used in CI

- full diff: https://github.com/containerd/containerd/compare/v1.7.13...v1.7.15
- release notes: https://github.com/containerd/containerd/releases/tag/v1.7.15

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-04-08 13:01:28 +02:00
Tianon Gravi
3b81ca4969
Merge pull request #47661 from cpuguy83/oci_tar_no_platform
save: Remove platform from config descriptor
2024-04-05 15:38:30 -07:00
Sebastiaan van Stijn
80572929e1
Merge pull request #47682 from vvoland/ci-check-changelog-error
ci/validate-pr: Use `::error::` command to print errors
2024-04-05 23:05:00 +02:00
Paweł Gronowski
fb92caf2aa
ci/validate-pr: Use ::error:: command to print errors
This will make Github render the log line as an error.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-04-05 14:56:50 +02:00
Paweł Gronowski
61269e718f
github/ci: Check if backport is opened against the expected branch
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-04-05 11:55:14 +02:00
Sebastiaan van Stijn
d25b0bd7ea
Merge pull request #47673 from thaJeztah/vendor_x_net
vendor: golang.org/x/net v0.23.0
2024-04-04 14:31:05 +02:00
Rob Murray
d8b768149b Move dummy DNS server to integration/internal/network
Signed-off-by: Rob Murray <rob.murray@docker.com>
2024-04-04 12:02:22 +01:00
Sebastiaan van Stijn
6d30487d2e
Merge pull request #47670 from vvoland/update-go
update to go1.21.9
2024-04-04 12:11:14 +02:00
Paweł Gronowski
329d403e20
update to go1.21.9
go1.21.9 (released 2024-04-03) includes a security fix to the net/http
package, as well as bug fixes to the linker, and the go/types and
net/http packages. See the [Go 1.21.9 milestone](https://github.com/golang/go/issues?q=milestone%3AGo1.21.9+label%3ACherryPickApproved)
for more details.

These minor releases include 1 security fixes following the security policy:

- http2: close connections when receiving too many headers

Maintaining HPACK state requires that we parse and process all HEADERS
and CONTINUATION frames on a connection. When a request's headers exceed
MaxHeaderBytes, we don't allocate memory to store the excess headers but
we do parse them. This permits an attacker to cause an HTTP/2 endpoint
to read arbitrary amounts of header data, all associated with a request
which is going to be rejected. These headers can include Huffman-encoded
data which is significantly more expensive for the receiver to decode
than for an attacker to send.

Set a limit on the amount of excess header frames we will process before
closing a connection.

Thanks to Bartek Nowotarski (https://nowotarski.info/) for reporting this issue.

This is CVE-2023-45288 and Go issue https://go.dev/issue/65051.

View the release notes for more information:
https://go.dev/doc/devel/release#go1.22.2

- https://github.com/golang/go/issues?q=milestone%3AGo1.21.9+label%3ACherryPickApproved
- full diff: https://github.com/golang/go/compare/go1.21.8...go1.21.9

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-04-04 10:13:50 +02:00
Sebastiaan van Stijn
d66589496e
vendor: golang.org/x/net v0.23.0
full diff: https://github.com/golang/net/compare/v0.22.0...v0.23.0

Includes a fix for CVE-2023-45288, which is also addressed in go1.22.2
and go1.21.9;

> http2: close connections when receiving too many headers
>
> Maintaining HPACK state requires that we parse and process
> all HEADERS and CONTINUATION frames on a connection.
> When a request's headers exceed MaxHeaderBytes, we don't
> allocate memory to store the excess headers but we do
> parse them. This permits an attacker to cause an HTTP/2
> endpoint to read arbitrary amounts of data, all associated
> with a request which is going to be rejected.
>
> Set a limit on the amount of excess header frames we
> will process before closing a connection.
>
> Thanks to Bartek Nowotarski for reporting this issue.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-04-03 20:42:29 +02:00
Sebastiaan van Stijn
e1ca74361b
vendor: golang.org/x/net v0.22.0, golang.org/x/crypto v0.21.0
full diffs changes relevant to vendored code:

- https://github.com/golang/net/compare/v0.18.0...v0.22.0
    - websocket: add support for dialing with context
    - http2: remove suspicious uint32->v conversion in frame code
    - http2: send an error of FLOW_CONTROL_ERROR when exceed the maximum octets
- https://github.com/golang/crypto/compare/v0.17.0...v0.21.0
    - internal/poly1305: drop Go 1.12 compatibility
    - internal/poly1305: improve sum_ppc64le.s
    - ocsp: don't use iota for externally defined constants

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-04-03 20:38:05 +02:00
Jasper Siepkes
cf933115b6
Minor fix for illumos support
illumos is the opensource continuation of OpenSolaris after Oracle
closed to source it (again).

For example use see: https://github.com/openbao/openbao/pull/205.

Signed-off-by: Jasper Siepkes <siepkes@serviceplanet.nl>
2024-04-03 15:58:27 +02:00
Albin Kerouanton
9fa76786ab
Merge pull request #47431 from akerouanton/api-normalize-default-NetworkMode
api: normalize the default NetworkMode
2024-04-03 15:44:24 +02:00
Brian Goff
9160b9fda6 save: Remove platform from config descriptor
This was brought up by bmitch that its not expected to have a platform
object in the config descriptor.
Also checked with tianon who agreed, its not _wrong_ but is unexpected
and doesn't neccessarily make sense to have it there.

Also, while technically incorrect, ECR is throwing an error when it sees
this.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
2024-04-02 17:15:52 +00:00
Paweł Gronowski
8599f2a3fb
Merge pull request #47658 from cpuguy83/fix_error_wrap_local_logs
Fix cases where we are wrapping a nil error
2024-04-02 10:28:09 +02:00
Brian Goff
0a48d26fbc Fix cases where we are wrapping a nil error
This was using `errors.Wrap` when there was no error to wrap, meanwhile
we are supposed to be creating a new error.

Found this while investigating some log corruption issues and
unexpectedly getting a nil reader and a nil error from `getTailReader`.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
2024-04-01 21:30:43 +00:00
Bjorn Neergaard
330a6f959f
Merge pull request #47645 from vvoland/community-slack
CONTRIBUTING.md: update Slack link
2024-03-28 14:25:58 -06:00
Albin Kerouanton
c4689034fd daemon: don't call NetworkMode.IsDefault()
Previous commit made this unnecessary.

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
2024-03-28 12:35:47 +01:00
Albin Kerouanton
4eed3dcdfe api: normalize the default NetworkMode
The NetworkMode "default" is now normalized into the value it
aliases ("bridge" on Linux and "nat" on Windows) by the
ContainerCreate endpoint, the legacy image builder, Swarm's
cluster executor and by the container restore codepath.

builder-next is left untouched as it already uses the normalized
value (ie. bridge).

Going forward, this will make maintenance easier as there's one
less NetworkMode to care about.

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
2024-03-28 12:34:23 +01:00
Paweł Gronowski
c187f95fe1
CONTRIBUTING.md: update Slack link
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-03-28 09:37:18 +01:00
Albin Kerouanton
a33b302d54
Merge pull request #47635 from robmry/backport-26.0/47619_restore_prestart_hook
[26.0 backport] Restore the SetKey prestart hook.
2024-03-28 08:24:48 +00:00
Paweł Gronowski
484480f56a
Merge pull request #47636 from crazy-max/rm-artifacts-upload
ci: update workflow artifacts retention
2024-03-27 12:30:30 +01:00
CrazyMax
aff003139c
ci: update workflow artifacts retention
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2024-03-27 10:57:01 +01:00
Rob Murray
1014f481de Restore the SetKey prestart hook.
Partially reverts 0046b16 "daemon: set libnetwork sandbox key w/o OCI hook"

Running SetKey to store the OCI Sandbox key after task creation, rather
than from the OCI prestart hook, meant it happened after sysctl settings
were applied by the runtime - which was the intention, we wanted to
complete Sandbox configuration after IPv6 had been disabled by a sysctl
if that was going to happen.

But, it meant '--sysctl' options for a specfic network interface caused
container task creation to fail, because the interface is only moved into
the network namespace during SetKey.

This change restores the SetKey prestart hook, and regenerates config
files that depend on the container's support for IPv6 after the task has
been created. It also adds a regression test that makes sure it's possible
to set an interface-specfic sysctl.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2024-03-27 08:50:25 +00:00
Albin Kerouanton
d57b899904
Merge pull request #47621 from robmry/47619_restore_prestart_hook
Restore the SetKey prestart hook.
2024-03-27 08:12:18 +00:00
Rob Murray
fde80fe2e7 Restore the SetKey prestart hook.
Partially reverts 0046b16 "daemon: set libnetwork sandbox key w/o OCI hook"

Running SetKey to store the OCI Sandbox key after task creation, rather
than from the OCI prestart hook, meant it happened after sysctl settings
were applied by the runtime - which was the intention, we wanted to
complete Sandbox configuration after IPv6 had been disabled by a sysctl
if that was going to happen.

But, it meant '--sysctl' options for a specfic network interface caused
container task creation to fail, because the interface is only moved into
the network namespace during SetKey.

This change restores the SetKey prestart hook, and regenerates config
files that depend on the container's support for IPv6 after the task has
been created. It also adds a regression test that makes sure it's possible
to set an interface-specfic sysctl.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2024-03-25 19:35:55 +00:00
Bjorn Neergaard
bfdb8918f9
Merge pull request #47613 from availhang/master
chore: fix mismatched function names in godoc
2024-03-22 15:48:57 -06:00
Sebastiaan van Stijn
83ae9927fb
Merge pull request #47603 from neersighted/authors_mailmap
AUTHORS,.mailmap: update with recent contributors
2024-03-22 10:13:28 +01:00
George Ma
14a8fac092 chore: fix mismatched function names in godoc
Signed-off-by: George Ma <mayangang@outlook.com>
2024-03-22 16:24:31 +08:00
Brian Goff
59c5059081
Merge pull request #47443 from corhere/cnmallocator/lift-n-shift
Vendor dependency cycle-free swarmkit
2024-03-21 12:29:46 -07:00
Sebastiaan van Stijn
1552e30a05
Merge pull request #47595 from tonistiigi/dockerfile-dlv-update
Dockerfile: avoid hardcoding arch combinations for delve
2024-03-21 15:46:47 +01:00
Paweł Gronowski
c64314fd55
Merge pull request #47610 from vvoland/dockerfile-cli-v26
Dockerfile: update docker CLI to v26.0.0
2024-03-21 15:35:41 +01:00
Bjorn Neergaard
61e2199b78
AUTHORS,.mailmap: update with recent contributors
Signed-off-by: Bjorn Neergaard <bjorn.neergaard@docker.com>
2024-03-21 07:34:37 -06:00
Paweł Gronowski
ea72f9f72c
Dockerfile: update docker CLI to v26.0.0
Update the CLI that's used in the dev-container

- full diff: https://github.com/docker/cli/compare/v26.0.0-rc2...v26.0.0

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-03-21 11:39:15 +01:00
Tonis Tiigi
f696e0d2a7
Dockerfile: avoid hardcoding arch combinations for delve
This is better because every possible platform combination
does not need to be defined in the Dockerfile. If built
for platform where Delve is not supported then it is just
skipped.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2024-03-19 10:22:35 -07:00
Cory Snider
7ebd88d2d9 hack: block imports of vendored testify packages
While github.com/stretchr/testify is not used directly by any of the
repository code, it is a transitive dependency via Swarmkit and
therefore still easy to use without having to revendor. Add lint rules
to ban importing testify packages to make sure nobody does.

Signed-off-by: Cory Snider <csnider@mirantis.com>
2024-02-29 16:20:15 -05:00
Cory Snider
4f30a930ad libn/cnmallocator: migrate tests to gotest.tools/v3
Apply command gotest.tools/v3/assert/cmd/gty-migrate-from-testify to the
cnmallocator package to be consistent with the assertion library used
elsewhere in moby.

Signed-off-by: Cory Snider <csnider@mirantis.com>
2024-02-29 16:14:02 -05:00
Cory Snider
7b0ab1011c Vendor dependency cycle-free swarmkit
Moby imports Swarmkit; Swarmkit no longer imports Moby. In order to
accomplish this feat, Swarmkit has introduced a new plugin.Getter
interface so it could stop importing our pkg/plugingetter package. This
new interface is not entirely compatible with our
plugingetter.PluginGetter interface, necessitating a thin adapter.

Swarmkit had to jettison the CNM network allocator to stop having to
import libnetwork as the cnmallocator package is deeply tied to
libnetwork. Move the CNM network allocator into libnetwork, where it
belongs. The package had a short an uninteresting Git history in the
Swarmkit repository so no effort was made to retain history.

Signed-off-by: Cory Snider <csnider@mirantis.com>
2024-02-28 09:46:45 -05:00
215 changed files with 32844 additions and 1377 deletions

View file

@ -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

View file

@ -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

View file

@ -13,7 +13,7 @@ on:
pull_request:
env:
GO_VERSION: "1.21.8"
GO_VERSION: "1.21.9"
DESTDIR: ./build
jobs:

View file

@ -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

View file

@ -13,7 +13,7 @@ on:
pull_request:
env:
GO_VERSION: "1.21.8"
GO_VERSION: "1.21.9"
GIT_PAGER: "cat"
PAGER: "cat"

View file

@ -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

View file

@ -51,6 +51,12 @@ linters-settings:
deny:
- pkg: io/ioutil
desc: The io/ioutil package has been deprecated, see https://go.dev/doc/go1.16#ioutil
- pkg: "github.com/stretchr/testify/assert"
desc: Use "gotest.tools/v3/assert" instead
- pkg: "github.com/stretchr/testify/require"
desc: Use "gotest.tools/v3/assert" instead
- pkg: "github.com/stretchr/testify/suite"
desc: Do not use
revive:
rules:
# FIXME make sure all packages have a description. Currently, there's many packages without.

View file

@ -173,6 +173,8 @@ Dattatraya Kumbhar <dattatraya.kumbhar@gslab.com>
Dave Goodchild <buddhamagnet@gmail.com>
Dave Henderson <dhenderson@gmail.com> <Dave.Henderson@ca.ibm.com>
Dave Tucker <dt@docker.com> <dave@dtucker.co.uk>
David Dooling <dooling@gmail.com>
David Dooling <dooling@gmail.com> <david.dooling@docker.com>
David M. Karr <davidmichaelkarr@gmail.com>
David Sheets <dsheets@docker.com> <sheets@alum.mit.edu>
David Sissitka <me@dsissitka.com>
@ -219,6 +221,8 @@ Felix Hupfeld <felix@quobyte.com> <quofelix@users.noreply.github.com>
Felix Ruess <felix.ruess@gmail.com> <felix.ruess@roboception.de>
Feng Yan <fy2462@gmail.com>
Fengtu Wang <wangfengtu@huawei.com> <wangfengtu@huawei.com>
Filipe Pina <hzlu1ot0@duck.com>
Filipe Pina <hzlu1ot0@duck.com> <636320+fopina@users.noreply.github.com>
Francisco Carriedo <fcarriedo@gmail.com>
Frank Rosquin <frank.rosquin+github@gmail.com> <frank.rosquin@gmail.com>
Frank Yang <yyb196@gmail.com>
@ -270,6 +274,7 @@ Hollie Teal <hollie@docker.com> <hollie.teal@docker.com>
Hollie Teal <hollie@docker.com> <hollietealok@users.noreply.github.com>
hsinko <21551195@zju.edu.cn> <hsinko@users.noreply.github.com>
Hu Keping <hukeping@huawei.com>
Huajin Tong <fliterdashen@gmail.com>
Hui Kang <hkang.sunysb@gmail.com>
Hui Kang <hkang.sunysb@gmail.com> <kangh@us.ibm.com>
Huu Nguyen <huu@prismskylabs.com> <whoshuu@gmail.com>
@ -563,6 +568,7 @@ Sebastiaan van Stijn <github@gone.nl> <sebastiaan@ws-key-sebas3.dpi1.dpi>
Sebastiaan van Stijn <github@gone.nl> <thaJeztah@users.noreply.github.com>
Sebastian Thomschke <sebthom@users.noreply.github.com>
Seongyeol Lim <seongyeol37@gmail.com>
Serhii Nakon <serhii.n@thescimus.com>
Shaun Kaasten <shaunk@gmail.com>
Shawn Landden <shawn@churchofgit.com> <shawnlandden@gmail.com>
Shengbo Song <thomassong@tencent.com>

View file

@ -669,6 +669,7 @@ Erik Hollensbe <github@hollensbe.org>
Erik Inge Bolsø <knan@redpill-linpro.com>
Erik Kristensen <erik@erikkristensen.com>
Erik Sipsma <erik@sipsma.dev>
Erik Sjölund <erik.sjolund@gmail.com>
Erik St. Martin <alakriti@gmail.com>
Erik Weathers <erikdw@gmail.com>
Erno Hopearuoho <erno.hopearuoho@gmail.com>
@ -731,6 +732,7 @@ Feroz Salam <feroz.salam@sourcegraph.com>
Ferran Rodenas <frodenas@gmail.com>
Filipe Brandenburger <filbranden@google.com>
Filipe Oliveira <contato@fmoliveira.com.br>
Filipe Pina <hzlu1ot0@duck.com>
Flavio Castelli <fcastelli@suse.com>
Flavio Crisciani <flavio.crisciani@docker.com>
Florian <FWirtz@users.noreply.github.com>
@ -875,6 +877,8 @@ Hsing-Yu (David) Chen <davidhsingyuchen@gmail.com>
hsinko <21551195@zju.edu.cn>
Hu Keping <hukeping@huawei.com>
Hu Tao <hutao@cn.fujitsu.com>
Huajin Tong <fliterdashen@gmail.com>
huang-jl <1046678590@qq.com>
HuanHuan Ye <logindaveye@gmail.com>
Huanzhong Zhang <zhanghuanzhong90@gmail.com>
Huayi Zhang <irachex@gmail.com>
@ -969,6 +973,7 @@ Jannick Fahlbusch <git@jf-projects.de>
Januar Wayong <januar@gmail.com>
Jared Biel <jared.biel@bolderthinking.com>
Jared Hocutt <jaredh@netapp.com>
Jaroslav Jindrak <dzejrou@gmail.com>
Jaroslaw Zabiello <hipertracker@gmail.com>
Jasmine Hegman <jasmine@jhegman.com>
Jason A. Donenfeld <Jason@zx2c4.com>
@ -1012,6 +1017,7 @@ Jeffrey Bolle <jeffreybolle@gmail.com>
Jeffrey Morgan <jmorganca@gmail.com>
Jeffrey van Gogh <jvg@google.com>
Jenny Gebske <jennifer@gebske.de>
Jeongseok Kang <piono623@naver.com>
Jeremy Chambers <jeremy@thehipbot.com>
Jeremy Grosser <jeremy@synack.me>
Jeremy Huntwork <jhuntwork@lightcubesolutions.com>
@ -1029,6 +1035,7 @@ Jezeniel Zapanta <jpzapanta22@gmail.com>
Jhon Honce <jhonce@redhat.com>
Ji.Zhilong <zhilongji@gmail.com>
Jian Liao <jliao@alauda.io>
Jian Zeng <anonymousknight96@gmail.com>
Jian Zhang <zhangjian.fnst@cn.fujitsu.com>
Jiang Jinyang <jjyruby@gmail.com>
Jianyong Wu <jianyong.wu@arm.com>
@ -1967,6 +1974,7 @@ Sergey Evstifeev <sergey.evstifeev@gmail.com>
Sergii Kabashniuk <skabashnyuk@codenvy.com>
Sergio Lopez <slp@redhat.com>
Serhat Gülçiçek <serhat25@gmail.com>
Serhii Nakon <serhii.n@thescimus.com>
SeungUkLee <lsy931106@gmail.com>
Sevki Hasirci <s@sevki.org>
Shane Canon <scanon@lbl.gov>
@ -2253,6 +2261,7 @@ VladimirAus <v_roudakov@yahoo.com>
Vladislav Kolesnikov <vkolesnikov@beget.ru>
Vlastimil Zeman <vlastimil.zeman@diffblue.com>
Vojtech Vitek (V-Teq) <vvitek@redhat.com>
voloder <110066198+voloder@users.noreply.github.com>
Walter Leibbrandt <github@wrl.co.za>
Walter Stanish <walter@pratyeka.org>
Wang Chao <chao.wang@ucloud.cn>

View file

@ -101,7 +101,7 @@ the contributors guide.
<td>
<p>
Register for the Docker Community Slack at
<a href="https://dockr.ly/slack" target="_blank">https://dockr.ly/slack</a>.
<a href="https://dockr.ly/comm-slack" target="_blank">https://dockr.ly/comm-slack</a>.
We use the #moby-project channel for general discussion, and there are separate channels for other Moby projects such as #containerd.
</p>
</td>

View file

@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1
# syntax=docker/dockerfile:1.7
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
@ -24,6 +24,12 @@ ARG DOCKER_STATIC=1
# specified here should match a current release.
ARG REGISTRY_VERSION=2.8.3
# delve is currently only supported on linux/amd64 and linux/arm64;
# https://github.com/go-delve/delve/blob/v1.8.1/pkg/proc/native/support_sentinel.go#L1-L6
ARG DELVE_SUPPORTED=${TARGETPLATFORM#linux/amd64} DELVE_SUPPORTED=${DELVE_SUPPORTED#linux/arm64}
ARG DELVE_SUPPORTED=${DELVE_SUPPORTED:+"unsupported"}
ARG DELVE_SUPPORTED=${DELVE_SUPPORTED:-"supported"}
# cross compilation helper
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
@ -144,7 +150,7 @@ RUN git init . && git remote add origin "https://github.com/go-delve/delve.git"
ARG DELVE_VERSION=v1.21.1
RUN git fetch -q --depth 1 origin "${DELVE_VERSION}" +refs/tags/*:refs/tags/* && git checkout -q FETCH_HEAD
FROM base AS delve-build
FROM base AS delve-supported
WORKDIR /usr/src/delve
ARG TARGETPLATFORM
RUN --mount=from=delve-src,src=/usr/src/delve,rw \
@ -155,16 +161,8 @@ RUN --mount=from=delve-src,src=/usr/src/delve,rw \
xx-verify /build/dlv
EOT
# delve is currently only supported on linux/amd64 and linux/arm64;
# https://github.com/go-delve/delve/blob/v1.8.1/pkg/proc/native/support_sentinel.go#L1-L6
FROM binary-dummy AS delve-windows
FROM binary-dummy AS delve-linux-arm
FROM binary-dummy AS delve-linux-ppc64le
FROM binary-dummy AS delve-linux-s390x
FROM delve-build AS delve-linux-amd64
FROM delve-build AS delve-linux-arm64
FROM delve-linux-${TARGETARCH} AS delve-linux
FROM delve-${TARGETOS} AS delve
FROM binary-dummy AS delve-unsupported
FROM delve-${DELVE_SUPPORTED} AS delve
FROM base AS tomll
# GOTOML_VERSION specifies the version of the tomll binary to build and install
@ -198,7 +196,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

View file

@ -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}"

View file

@ -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.

View file

@ -456,15 +456,27 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
if hostConfig == nil {
hostConfig = &container.HostConfig{}
}
if hostConfig.NetworkMode == "" {
hostConfig.NetworkMode = "default"
}
if networkingConfig == nil {
networkingConfig = &network.NetworkingConfig{}
}
if networkingConfig.EndpointsConfig == nil {
networkingConfig.EndpointsConfig = make(map[string]*network.EndpointSettings)
}
// The NetworkMode "default" is used as a way to express a container should
// be attached to the OS-dependant default network, in an OS-independent
// way. Doing this conversion as soon as possible ensures we have less
// NetworkMode to handle down the path (including in the
// backward-compatibility layer we have just below).
//
// Note that this is not the only place where this conversion has to be
// done (as there are various other places where containers get created).
if hostConfig.NetworkMode == "" || hostConfig.NetworkMode.IsDefault() {
hostConfig.NetworkMode = runconfig.DefaultDaemonNetworkMode()
if nw, ok := networkingConfig.EndpointsConfig[network.NetworkDefault]; ok {
networkingConfig.EndpointsConfig[hostConfig.NetworkMode.NetworkName()] = nw
delete(networkingConfig.EndpointsConfig, network.NetworkDefault)
}
}
version := httputils.VersionFromContext(ctx)
@ -646,7 +658,7 @@ func handleMACAddressBC(config *container.Config, hostConfig *container.HostConf
}
return "", nil
}
if !hostConfig.NetworkMode.IsDefault() && !hostConfig.NetworkMode.IsBridge() && !hostConfig.NetworkMode.IsUserDefined() {
if !hostConfig.NetworkMode.IsBridge() && !hostConfig.NetworkMode.IsUserDefined() {
return "", runconfig.ErrConflictContainerNetworkAndMac
}
@ -675,7 +687,7 @@ func handleMACAddressBC(config *container.Config, hostConfig *container.HostConf
return "", nil
}
var warning string
if hostConfig.NetworkMode.IsDefault() || hostConfig.NetworkMode.IsBridge() || hostConfig.NetworkMode.IsUserDefined() {
if hostConfig.NetworkMode.IsBridge() || hostConfig.NetworkMode.IsUserDefined() {
nwName := hostConfig.NetworkMode.NetworkName()
// If there's no endpoint config, create a place to store the configured address.
if len(networkingConfig.EndpointsConfig) == 0 {

View file

@ -22,7 +22,7 @@ func normalizeWorkdir(_ string, current string, requested string) (string, error
if !filepath.IsAbs(requested) {
return filepath.Join(string(os.PathSeparator), current, requested), nil
}
return requested, nil
return filepath.Clean(requested), nil
}
// resolveCmdLine takes a command line arg set and optionally prepends a platform-specific

View file

@ -15,11 +15,13 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/backend"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/builder"
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/chrootarchive"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/runconfig"
"github.com/docker/go-connections/nat"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
@ -377,12 +379,21 @@ func hostConfigFromOptions(options *types.ImageBuildOptions) *container.HostConf
Ulimits: options.Ulimits,
}
// We need to make sure no empty string or "default" NetworkMode is
// provided to the daemon as it doesn't support them.
//
// This is in line with what the ContainerCreate API endpoint does.
networkMode := options.NetworkMode
if networkMode == "" || networkMode == network.NetworkDefault {
networkMode = runconfig.DefaultDaemonNetworkMode().NetworkName()
}
hc := &container.HostConfig{
SecurityOpt: options.SecurityOpt,
Isolation: options.Isolation,
ShmSize: options.ShmSize,
Resources: resources,
NetworkMode: container.NetworkMode(options.NetworkMode),
NetworkMode: container.NetworkMode(networkMode),
// Set a log config to override any default value set on the daemon
LogConfig: defaultLogConfig,
ExtraHosts: options.ExtraHosts,

View file

@ -299,8 +299,8 @@ func (container *Container) SetupWorkingDirectory(rootIdentity idtools.Identity)
return nil
}
container.Config.WorkingDir = filepath.Clean(container.Config.WorkingDir)
pth, err := container.GetResourcePath(container.Config.WorkingDir)
workdir := filepath.Clean(container.Config.WorkingDir)
pth, err := container.GetResourcePath(workdir)
if err != nil {
return err
}

View file

@ -120,8 +120,9 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) *types.ContainerSpec {
if m.VolumeOptions != nil {
mount.VolumeOptions = &mounttypes.VolumeOptions{
NoCopy: m.VolumeOptions.NoCopy,
Labels: m.VolumeOptions.Labels,
NoCopy: m.VolumeOptions.NoCopy,
Labels: m.VolumeOptions.Labels,
Subpath: m.VolumeOptions.Subpath,
}
if m.VolumeOptions.DriverConfig != nil {
mount.VolumeOptions.DriverConfig = &mounttypes.Driver{
@ -406,8 +407,9 @@ func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
if m.VolumeOptions != nil {
mount.VolumeOptions = &swarmapi.Mount_VolumeOptions{
NoCopy: m.VolumeOptions.NoCopy,
Labels: m.VolumeOptions.Labels,
NoCopy: m.VolumeOptions.NoCopy,
Labels: m.VolumeOptions.Labels,
Subpath: m.VolumeOptions.Subpath,
}
if m.VolumeOptions.DriverConfig != nil {
mount.VolumeOptions.DriverConfig = &swarmapi.Driver{

View file

@ -0,0 +1,42 @@
package convert
import (
"github.com/docker/docker/pkg/plugingetter"
"github.com/moby/swarmkit/v2/node/plugin"
)
// SwarmPluginGetter adapts a plugingetter.PluginGetter to a Swarmkit plugin.Getter.
func SwarmPluginGetter(pg plugingetter.PluginGetter) plugin.Getter {
return pluginGetter{pg}
}
type pluginGetter struct {
pg plugingetter.PluginGetter
}
var _ plugin.Getter = (*pluginGetter)(nil)
type swarmPlugin struct {
plugingetter.CompatPlugin
}
func (p swarmPlugin) Client() plugin.Client {
return p.CompatPlugin.Client()
}
func (g pluginGetter) Get(name string, capability string) (plugin.Plugin, error) {
p, err := g.pg.Get(name, capability, plugingetter.Lookup)
if err != nil {
return nil, err
}
return swarmPlugin{p}, nil
}
func (g pluginGetter) GetAllManagedPluginsByCap(capability string) []plugin.Plugin {
pp := g.pg.GetAllManagedPluginsByCap(capability)
ret := make([]plugin.Plugin, len(pp))
for i, p := range pp {
ret[i] = swarmPlugin{p}
}
return ret
}

View file

@ -4,11 +4,13 @@ import (
"testing"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/mount"
swarmtypes "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/swarm/runtime"
google_protobuf3 "github.com/gogo/protobuf/types"
swarmapi "github.com/moby/swarmkit/v2/api"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestServiceConvertFromGRPCRuntimeContainer(t *testing.T) {
@ -611,3 +613,32 @@ func TestServiceConvertToGRPCConfigs(t *testing.T) {
})
}
}
func TestServiceConvertToGRPCVolumeSubpath(t *testing.T) {
s := swarmtypes.ServiceSpec{
TaskTemplate: swarmtypes.TaskSpec{
ContainerSpec: &swarmtypes.ContainerSpec{
Mounts: []mount.Mount{
{
Source: "/foo/bar",
Target: "/baz",
Type: mount.TypeVolume,
ReadOnly: false,
VolumeOptions: &mount.VolumeOptions{
Subpath: "sub",
},
},
},
},
},
}
g, err := ServiceSpecToGRPC(s)
assert.NilError(t, err)
v, ok := g.Task.Runtime.(*swarmapi.TaskSpec_Container)
assert.Assert(t, ok)
assert.Check(t, is.Len(v.Container.Mounts, 1))
assert.Check(t, is.Equal(v.Container.Mounts[0].VolumeOptions.Subpath, "sub"))
}

View file

@ -17,12 +17,14 @@ import (
"github.com/docker/docker/api/types/backend"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/registry"
containerpkg "github.com/docker/docker/container"
"github.com/docker/docker/daemon"
"github.com/docker/docker/daemon/cluster/convert"
executorpkg "github.com/docker/docker/daemon/cluster/executor"
"github.com/docker/docker/libnetwork"
"github.com/docker/docker/runconfig"
volumeopts "github.com/docker/docker/volume/service/opts"
gogotypes "github.com/gogo/protobuf/types"
"github.com/moby/swarmkit/v2/agent/exec"
@ -290,14 +292,34 @@ func (c *containerAdapter) waitForDetach(ctx context.Context) error {
}
func (c *containerAdapter) create(ctx context.Context) error {
hostConfig := c.container.hostConfig(c.dependencies.Volumes())
netConfig := c.container.createNetworkingConfig(c.backend)
// We need to make sure no empty string or "default" NetworkMode is
// provided to the daemon as it doesn't support them.
//
// This is in line with what the ContainerCreate API endpoint does, but
// unlike that endpoint we can't do that in the ServiceCreate endpoint as
// the cluster leader and the current node might not be running on the same
// OS. Since the normalized value isn't the same on Windows and Linux, we
// need to make this normalization happen once we're sure we won't make a
// cross-OS API call.
if hostConfig.NetworkMode == "" || hostConfig.NetworkMode.IsDefault() {
hostConfig.NetworkMode = runconfig.DefaultDaemonNetworkMode()
if v, ok := netConfig.EndpointsConfig[network.NetworkDefault]; ok {
delete(netConfig.EndpointsConfig, network.NetworkDefault)
netConfig.EndpointsConfig[runconfig.DefaultDaemonNetworkMode().NetworkName()] = v
}
}
var cr containertypes.CreateResponse
var err error
if cr, err = c.backend.CreateManagedContainer(ctx, backend.ContainerCreateConfig{
Name: c.container.name(),
Config: c.container.config(),
HostConfig: c.container.hostConfig(c.dependencies.Volumes()),
HostConfig: hostConfig,
// Use the first network in container create
NetworkingConfig: c.container.createNetworkingConfig(c.backend),
NetworkingConfig: netConfig,
}); err != nil {
return err
}

View file

@ -338,7 +338,8 @@ func convertMount(m api.Mount) enginemount.Mount {
if m.VolumeOptions != nil {
mount.VolumeOptions = &enginemount.VolumeOptions{
NoCopy: m.VolumeOptions.NoCopy,
NoCopy: m.VolumeOptions.NoCopy,
Subpath: m.VolumeOptions.Subpath,
}
if m.VolumeOptions.Labels != nil {
mount.VolumeOptions.Labels = make(map[string]string, len(m.VolumeOptions.Labels))

View file

@ -52,7 +52,7 @@ func NewExecutor(b executorpkg.Backend, p plugin.Backend, i executorpkg.ImageBac
pluginBackend: p,
imageBackend: i,
volumeBackend: v,
dependencies: agent.NewDependencyManager(b.PluginGetter()),
dependencies: agent.NewDependencyManager(convert.SwarmPluginGetter(b.PluginGetter())),
}
}

View file

@ -10,10 +10,12 @@ import (
"github.com/containerd/log"
types "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/daemon/cluster/convert"
"github.com/docker/docker/daemon/cluster/executor/container"
lncluster "github.com/docker/docker/libnetwork/cluster"
"github.com/docker/docker/libnetwork/cnmallocator"
swarmapi "github.com/moby/swarmkit/v2/api"
swarmallocator "github.com/moby/swarmkit/v2/manager/allocator/cnmallocator"
"github.com/moby/swarmkit/v2/manager/allocator/networkallocator"
swarmnode "github.com/moby/swarmkit/v2/node"
"github.com/pkg/errors"
"google.golang.org/grpc"
@ -123,7 +125,7 @@ func (n *nodeRunner) start(conf nodeStartConfig) error {
ListenControlAPI: control,
ListenRemoteAPI: conf.ListenAddr,
AdvertiseRemoteAPI: conf.AdvertiseAddr,
NetworkConfig: &swarmallocator.NetworkConfig{
NetworkConfig: &networkallocator.Config{
DefaultAddrPool: conf.DefaultAddressPool,
SubnetSize: conf.SubnetSize,
VXLANUDPPort: conf.DataPathPort,
@ -144,7 +146,8 @@ func (n *nodeRunner) start(conf nodeStartConfig) error {
ElectionTick: n.cluster.config.RaftElectionTick,
UnlockKey: conf.lockKey,
AutoLockManagers: conf.autolock,
PluginGetter: n.cluster.config.Backend.PluginGetter(),
PluginGetter: convert.SwarmPluginGetter(n.cluster.config.Backend.PluginGetter()),
NetworkProvider: cnmallocator.NewProvider(n.cluster.config.Backend.PluginGetter()),
}
if conf.availability != "" {
avail, ok := swarmapi.NodeSpec_Availability_value[strings.ToUpper(string(conf.availability))]

View file

@ -196,6 +196,10 @@ func (conf *Config) ValidatePlatformConfig() error {
return errors.Wrap(err, "invalid fixed-cidr-v6")
}
if _, ok := conf.Features["windows-dns-proxy"]; ok {
return errors.New("feature option 'windows-dns-proxy' is only available on Windows")
}
return verifyDefaultCgroupNsMode(conf.CgroupNamespaceMode)
}

View file

@ -364,6 +364,6 @@ func translateWorkingDir(config *containertypes.Config) error {
if !system.IsAbs(wd) {
return fmt.Errorf("the working directory '%s' is invalid, it needs to be an absolute path", config.WorkingDir)
}
config.WorkingDir = wd
config.WorkingDir = filepath.Clean(wd)
return nil
}

View file

@ -54,7 +54,8 @@ func (daemon *Daemon) buildSandboxOptions(cfg *config.Config, container *contain
sboxOptions = append(sboxOptions, libnetwork.OptionUseExternalKey())
}
if err := setupPathsAndSandboxOptions(container, cfg, &sboxOptions); err != nil {
// Add platform-specific Sandbox options.
if err := buildSandboxPlatformOptions(container, cfg, &sboxOptions); err != nil {
return nil, err
}
@ -420,9 +421,6 @@ func (daemon *Daemon) updateContainerNetworkSettings(container *container.Contai
}
networkName := mode.NetworkName()
if mode.IsDefault() {
networkName = daemon.netController.Config().DefaultNetwork
}
if mode.IsUserDefined() {
var err error
@ -461,15 +459,6 @@ func (daemon *Daemon) updateContainerNetworkSettings(container *container.Contai
}
}
// Convert any settings added by client in default name to
// engine's default network name key
if mode.IsDefault() {
if nConf, ok := container.NetworkSettings.Networks[mode.NetworkName()]; ok {
container.NetworkSettings.Networks[networkName] = nConf
delete(container.NetworkSettings.Networks, mode.NetworkName())
}
}
if !mode.IsUserDefined() {
return
}

View file

@ -417,7 +417,7 @@ func serviceDiscoveryOnDefaultNetwork() bool {
return false
}
func setupPathsAndSandboxOptions(container *container.Container, cfg *config.Config, sboxOptions *[]libnetwork.SandboxOption) error {
func buildSandboxPlatformOptions(container *container.Container, cfg *config.Config, sboxOptions *[]libnetwork.SandboxOption) error {
var err error
var originResolvConfPath string
@ -481,6 +481,7 @@ func setupPathsAndSandboxOptions(container *container.Container, cfg *config.Con
return err
}
*sboxOptions = append(*sboxOptions, libnetwork.OptionResolvConfPath(container.ResolvConfPath))
return nil
}

View file

@ -163,7 +163,13 @@ func serviceDiscoveryOnDefaultNetwork() bool {
return true
}
func setupPathsAndSandboxOptions(container *container.Container, cfg *config.Config, sboxOptions *[]libnetwork.SandboxOption) error {
func buildSandboxPlatformOptions(container *container.Container, cfg *config.Config, sboxOptions *[]libnetwork.SandboxOption) error {
// By default, the Windows internal resolver does not forward requests to
// external resolvers - but forwarding can be enabled using feature flag
// "windows-dns-proxy":true.
if doproxy, exists := cfg.Features["windows-dns-proxy"]; !exists || !doproxy {
*sboxOptions = append(*sboxOptions, libnetwork.OptionDNSNoProxy())
}
return nil
}

View file

@ -45,7 +45,7 @@ func (i *ImageService) walkImageManifests(ctx context.Context, img containerdima
return i.walkPresentChildren(ctx, desc, handleManifest)
}
return errNotManifestOrIndex
return errors.Wrapf(errNotManifestOrIndex, "error walking manifest for %s", img.Name)
}
type ImageManifest struct {

View file

@ -32,6 +32,7 @@ import (
"github.com/docker/docker/api/types/backend"
containertypes "github.com/docker/docker/api/types/container"
imagetypes "github.com/docker/docker/api/types/image"
networktypes "github.com/docker/docker/api/types/network"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/volume"
@ -373,6 +374,21 @@ func (daemon *Daemon) restore(cfg *configStore) error {
Type: local.Name,
}
}
// Normalize the "default" network mode into the network mode
// it aliases ("bridge on Linux and "nat" on Windows). This is
// also done by the container router, for new containers. But
// we need to do it here too to handle containers that were
// created prior to v26.0.
//
// TODO(aker): remove this migration code once the next LTM version of MCR is released.
if c.HostConfig.NetworkMode.IsDefault() {
c.HostConfig.NetworkMode = runconfig.DefaultDaemonNetworkMode()
if nw, ok := c.NetworkSettings.Networks[networktypes.NetworkDefault]; ok {
c.NetworkSettings.Networks[c.HostConfig.NetworkMode.NetworkName()] = nw
delete(c.NetworkSettings.Networks, networktypes.NetworkDefault)
}
}
}
if err := daemon.checkpointAndSave(c); err != nil {

View file

@ -104,8 +104,8 @@ func getMemoryResources(config containertypes.Resources) *specs.LinuxMemory {
memory.DisableOOMKiller = config.OomKillDisable
}
if config.KernelMemory != 0 {
memory.Kernel = &config.KernelMemory
if config.KernelMemory != 0 { //nolint:staticcheck // ignore SA1019: memory.Kernel is deprecated: kernel-memory limits are not supported in cgroups v2, and were obsoleted in [kernel v5.4]. This field should no longer be used, as it may be ignored by runtimes.
memory.Kernel = &config.KernelMemory //nolint:staticcheck // ignore SA1019: memory.Kernel is deprecated: kernel-memory limits are not supported in cgroups v2, and were obsoleted in [kernel v5.4]. This field should no longer be used, as it may be ignored by runtimes.
}
if config.KernelMemoryTCP != 0 {

View file

@ -43,9 +43,14 @@ func (i *ImageService) ImageHistory(ctx context.Context, name string) ([]*image.
layerCounter++
}
var created int64
if h.Created != nil {
created = h.Created.Unix()
}
history = append([]*image.HistoryResponseItem{{
ID: "<missing>",
Created: h.Created.Unix(),
Created: created,
CreatedBy: h.CreatedBy,
Comment: h.Comment,
Size: layerSize,

View file

@ -130,12 +130,8 @@ func (daemon *Daemon) getInspectData(daemonCfg *config.Config, container *contai
// unconditionally, to keep backward compatibility with clients that use
// unversioned API endpoints.
if container.Config != nil && container.Config.MacAddress == "" { //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
if nwm := hostConfig.NetworkMode; nwm.IsDefault() || nwm.IsBridge() || nwm.IsUserDefined() {
name := nwm.NetworkName()
if nwm.IsDefault() {
name = daemon.netController.Config().DefaultNetwork
}
if epConf, ok := container.NetworkSettings.Networks[name]; ok {
if nwm := hostConfig.NetworkMode; nwm.IsBridge() || nwm.IsUserDefined() {
if epConf, ok := container.NetworkSettings.Networks[nwm.NetworkName()]; ok {
container.Config.MacAddress = epConf.DesiredMacAddress //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
}
}

View file

@ -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++

View file

@ -83,7 +83,13 @@ func setNvidiaGPUs(s *specs.Spec, dev *deviceInstance) error {
if s.Hooks == nil {
s.Hooks = &specs.Hooks{}
}
s.Hooks.Prestart = append(s.Hooks.Prestart, specs.Hook{
// This implementation uses prestart hooks, which are deprecated.
// CreateRuntime is the closest equivalent, and executed in the same
// locations as prestart-hooks, but depending on what these hooks do,
// possibly one of the other hooks could be used instead (such as
// CreateContainer or StartContainer).
s.Hooks.Prestart = append(s.Hooks.Prestart, specs.Hook{ //nolint:staticcheck // FIXME(thaJeztah); replace prestart hook with a non-deprecated one.
Path: path,
Args: []string{
nvidiaHook,

View file

@ -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{ //nolint:staticcheck // FIXME(thaJeztah); replace prestart hook with a non-deprecated one.
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),

View file

@ -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

View file

@ -59,8 +59,8 @@ func toContainerdResources(resources container.Resources) *libcontainerdtypes.Re
if resources.MemoryReservation != 0 {
memory.Reservation = &resources.MemoryReservation
}
if resources.KernelMemory != 0 {
memory.Kernel = &resources.KernelMemory
if resources.KernelMemory != 0 { //nolint:staticcheck // ignore SA1019: memory.Kernel is deprecated: kernel-memory limits are not supported in cgroups v2, and were obsoleted in [kernel v5.4]. This field should no longer be used, as it may be ignored by runtimes.
memory.Kernel = &resources.KernelMemory //nolint:staticcheck // ignore SA1019: memory.Kernel is deprecated: kernel-memory limits are not supported in cgroups v2, and were obsoleted in [kernel v5.4]. This field should no longer be used, as it may be ignored by runtimes.
}
if resources.MemorySwap > 0 {
memory.Swap = &resources.MemorySwap

View file

@ -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"

View file

@ -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

View file

@ -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,
}

View file

@ -4296,7 +4296,7 @@ func (s *DockerCLIBuildSuite) TestBuildBuildTimeArgExpansion(c *testing.T) {
imgName := "bldvarstest"
wdVar := "WDIR"
wdVal := "/tmp/"
wdVal := "/tmp"
addVar := "AFILE"
addVal := "addFile"
copyVar := "CFILE"

View file

@ -689,6 +689,72 @@ func TestBuildPlatformInvalid(t *testing.T) {
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
}
// TestBuildWorkdirNoCacheMiss is a regression test for https://github.com/moby/moby/issues/47627
func TestBuildWorkdirNoCacheMiss(t *testing.T) {
ctx := setupTest(t)
for _, tc := range []struct {
name string
dockerfile string
}{
{name: "trailing slash", dockerfile: "FROM busybox\nWORKDIR /foo/"},
{name: "no trailing slash", dockerfile: "FROM busybox\nWORKDIR /foo"},
} {
dockerfile := tc.dockerfile
t.Run(tc.name, func(t *testing.T) {
source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile))
defer source.Close()
apiClient := testEnv.APIClient()
buildAndGetID := func() string {
resp, err := apiClient.ImageBuild(ctx, source.AsTarReader(t), types.ImageBuildOptions{
Version: types.BuilderV1,
})
assert.NilError(t, err)
defer resp.Body.Close()
id := readBuildImageIDs(t, resp.Body)
assert.Check(t, id != "")
return id
}
firstId := buildAndGetID()
secondId := buildAndGetID()
assert.Check(t, is.Equal(firstId, secondId), "expected cache to be used")
})
}
}
func readBuildImageIDs(t *testing.T, rd io.Reader) string {
t.Helper()
decoder := json.NewDecoder(rd)
for {
var jm jsonmessage.JSONMessage
if err := decoder.Decode(&jm); err != nil {
if err == io.EOF {
break
}
assert.NilError(t, err)
}
if jm.Aux == nil {
continue
}
var auxId struct {
ID string `json:"ID"`
}
if json.Unmarshal(*jm.Aux, &auxId); auxId.ID != "" {
return auxId.ID
}
}
return ""
}
func writeTarRecord(t *testing.T, w *tar.Writer, fn, contents string) {
err := w.WriteHeader(&tar.Header{
Name: fn,

View file

@ -326,3 +326,33 @@ func TestStaticIPOutsideSubpool(t *testing.T) {
assert.Check(t, is.Contains(b.String(), "inet 10.42.1.3/16"))
}
func TestWorkingDirNormalization(t *testing.T) {
ctx := setupTest(t)
apiClient := testEnv.APIClient()
for _, tc := range []struct {
name string
workdir string
}{
{name: "trailing slash", workdir: "/tmp/"},
{name: "no trailing slash", workdir: "/tmp"},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
cID := container.Run(ctx, t, apiClient,
container.WithImage("busybox"),
container.WithWorkingDir(tc.workdir),
)
defer container.Remove(ctx, t, apiClient, cID, containertypes.RemoveOptions{Force: true})
inspect := container.Inspect(ctx, t, apiClient, cID)
assert.Check(t, is.Equal(inspect.Config.WorkingDir, "/tmp"))
})
}
}

View 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() })
}

View file

@ -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
}
}

View file

@ -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"))
}
})
}
}

View file

@ -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))
}

View file

@ -1,38 +1,20 @@
package networking
import (
"net"
"os"
"context"
"strings"
"testing"
"time"
containertypes "github.com/docker/docker/api/types/container"
"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 +22,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 +63,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 +73,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 +104,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 +113,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 +131,29 @@ 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))
}
// TestNslookupWindows checks that nslookup gets results from external DNS.
// Regression test for https://github.com/moby/moby/issues/46792
func TestNslookupWindows(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "windows")
ctx := setupTest(t)
c := testEnv.APIClient()
attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
res := container.RunAttach(attachCtx, t, c,
container.WithCmd("nslookup", "docker.com"),
)
defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{Force: true})
assert.Check(t, is.Equal(res.ExitCode, 0))
// Current default is to not-forward requests to external servers, which
// can only be changed in daemon.json using feature flag "windows-dns-proxy".
// So, expect the lookup to fail...
assert.Check(t, is.Contains(res.Stderr.String(), "Server failed"))
// When the default behaviour is changed, nslookup should succeed...
//assert.Check(t, is.Contains(res.Stdout.String(), "Addresses:"))
}

View file

@ -0,0 +1,14 @@
package cnmallocator
import (
"runtime"
"testing"
"github.com/moby/swarmkit/v2/manager/allocator"
"gotest.tools/v3/skip"
)
func TestAllocator(t *testing.T) {
skip.If(t, runtime.GOOS == "windows", "Allocator tests are hardcoded to use Linux network driver names")
allocator.RunAllocatorTests(t, NewProvider(nil))
}

View file

@ -11,6 +11,6 @@ var initializers = map[string]func(driverapi.Registerer) error{
}
// PredefinedNetworks returns the list of predefined network structures
func PredefinedNetworks() []networkallocator.PredefinedNetworkData {
func (*Provider) PredefinedNetworks() []networkallocator.PredefinedNetworkData {
return nil
}

View file

@ -5,14 +5,15 @@ import (
"strconv"
"strings"
"github.com/containerd/log"
"github.com/docker/docker/libnetwork/ipamapi"
builtinIpam "github.com/docker/docker/libnetwork/ipams/builtin"
nullIpam "github.com/docker/docker/libnetwork/ipams/null"
"github.com/docker/docker/libnetwork/ipamutils"
"github.com/moby/swarmkit/v2/log"
"github.com/moby/swarmkit/v2/manager/allocator/networkallocator"
)
func initIPAMDrivers(r ipamapi.Registerer, netConfig *NetworkConfig) error {
func initIPAMDrivers(r ipamapi.Registerer, netConfig *networkallocator.Config) error {
var addressPool []*ipamutils.NetworkToSplit
var str strings.Builder
str.WriteString("Subnetlist - ")

View file

@ -19,7 +19,7 @@ var initializers = map[string]func(driverapi.Registerer) error{
}
// PredefinedNetworks returns the list of predefined network structures
func PredefinedNetworks() []networkallocator.PredefinedNetworkData {
func (*Provider) PredefinedNetworks() []networkallocator.PredefinedNetworkData {
return []networkallocator.PredefinedNetworkData{
{Name: "bridge", Driver: "bridge"},
{Name: "host", Driver: "host"},

View file

@ -14,7 +14,7 @@ var initializers = map[string]func(driverapi.Registerer) error{
}
// PredefinedNetworks returns the list of predefined network structures
func PredefinedNetworks() []networkallocator.PredefinedNetworkData {
func (*Provider) PredefinedNetworks() []networkallocator.PredefinedNetworkData {
return []networkallocator.PredefinedNetworkData{
{Name: "nat", Driver: "nat"},
}

View file

@ -10,6 +10,6 @@ import (
const initializers = nil
// PredefinedNetworks returns the list of predefined network structures
func PredefinedNetworks() []networkallocator.PredefinedNetworkData {
func (*Provider) PredefinedNetworks() []networkallocator.PredefinedNetworkData {
return nil
}

View file

@ -6,6 +6,7 @@ import (
"net"
"strings"
"github.com/containerd/log"
"github.com/docker/docker/libnetwork/driverapi"
"github.com/docker/docker/libnetwork/drivers/remote"
"github.com/docker/docker/libnetwork/drvregistry"
@ -15,7 +16,6 @@ import (
"github.com/docker/docker/libnetwork/scope"
"github.com/docker/docker/pkg/plugingetter"
"github.com/moby/swarmkit/v2/api"
"github.com/moby/swarmkit/v2/log"
"github.com/moby/swarmkit/v2/manager/allocator/networkallocator"
"github.com/pkg/errors"
)
@ -40,9 +40,6 @@ type cnmNetworkAllocator struct {
// The driver registry for all internal and external network drivers.
networkRegistry drvregistry.Networks
// The port allocator instance for allocating node ports
portAllocator *portAllocator
// Local network state used by cnmNetworkAllocator to do network management.
networks map[string]*network
@ -87,27 +84,14 @@ type networkDriver struct {
capability *driverapi.Capability
}
// NetworkConfig is used to store network related cluster config in the Manager.
type NetworkConfig struct {
// DefaultAddrPool specifies default subnet pool for global scope networks
DefaultAddrPool []string
// SubnetSize specifies the subnet size of the networks created from
// the default subnet pool
SubnetSize uint32
// VXLANUDPPort specifies the UDP port number for VXLAN traffic
VXLANUDPPort uint32
}
// New returns a new NetworkAllocator handle
func New(pg plugingetter.PluginGetter, netConfig *NetworkConfig) (networkallocator.NetworkAllocator, error) {
// NewAllocator returns a new NetworkAllocator handle
func (p *Provider) NewAllocator(netConfig *networkallocator.Config) (networkallocator.NetworkAllocator, error) {
na := &cnmNetworkAllocator{
networks: make(map[string]*network),
services: make(map[string]struct{}),
tasks: make(map[string]struct{}),
nodes: make(map[string]map[string]struct{}),
pg: pg,
pg: p.pg,
}
for ntype, i := range initializers {
@ -115,23 +99,17 @@ func New(pg plugingetter.PluginGetter, netConfig *NetworkConfig) (networkallocat
return nil, fmt.Errorf("failed to register %q network driver: %w", ntype, err)
}
}
if err := remote.Register(&na.networkRegistry, pg); err != nil {
if err := remote.Register(&na.networkRegistry, p.pg); err != nil {
return nil, fmt.Errorf("failed to initialize network driver plugins: %w", err)
}
if err := initIPAMDrivers(&na.ipamRegistry, netConfig); err != nil {
return nil, err
}
if err := remoteipam.Register(&na.ipamRegistry, pg); err != nil {
if err := remoteipam.Register(&na.ipamRegistry, p.pg); err != nil {
return nil, fmt.Errorf("failed to initialize IPAM driver plugins: %w", err)
}
pa, err := newPortAllocator()
if err != nil {
return nil, err
}
na.portAllocator = pa
return na, nil
}
@ -209,11 +187,8 @@ func (na *cnmNetworkAllocator) Deallocate(n *api.Network) error {
}
// AllocateService allocates all the network resources such as virtual
// IP and ports needed by the service.
// IP needed by the service.
func (na *cnmNetworkAllocator) AllocateService(s *api.Service) (err error) {
if err = na.portAllocator.serviceAllocatePorts(s); err != nil {
return err
}
defer func() {
if err != nil {
na.DeallocateService(s)
@ -300,7 +275,7 @@ networkLoop:
}
// DeallocateService de-allocates all the network resources such as
// virtual IP and ports associated with the service.
// virtual IP associated with the service.
func (na *cnmNetworkAllocator) DeallocateService(s *api.Service) error {
if s.Endpoint == nil {
return nil
@ -316,7 +291,6 @@ func (na *cnmNetworkAllocator) DeallocateService(s *api.Service) error {
}
s.Endpoint.VirtualIPs = nil
na.portAllocator.serviceDeallocatePorts(s)
delete(na.services, s.ID)
return nil
@ -373,19 +347,8 @@ func (na *cnmNetworkAllocator) IsTaskAllocated(t *api.Task) bool {
return true
}
// HostPublishPortsNeedUpdate returns true if the passed service needs
// allocations for its published ports in host (non ingress) mode
func (na *cnmNetworkAllocator) HostPublishPortsNeedUpdate(s *api.Service) bool {
return na.portAllocator.hostPublishPortsNeedUpdate(s)
}
// IsServiceAllocated returns false if the passed service needs to have network resources allocated/updated.
func (na *cnmNetworkAllocator) IsServiceAllocated(s *api.Service, flags ...func(*networkallocator.ServiceAllocationOpts)) bool {
var options networkallocator.ServiceAllocationOpts
for _, flag := range flags {
flag(&options)
}
specNetworks := serviceNetworks(s)
// If endpoint mode is VIP and allocator does not have the
@ -447,10 +410,6 @@ func (na *cnmNetworkAllocator) IsServiceAllocated(s *api.Service, flags ...func(
}
}
if (s.Spec.Endpoint != nil && len(s.Spec.Endpoint.Ports) != 0) ||
(s.Endpoint != nil && len(s.Endpoint.Ports) != 0) {
return na.portAllocator.isPortsAllocatedOnInit(s, options.OnInit)
}
return true
}

View file

@ -0,0 +1,789 @@
package cnmallocator
import (
"fmt"
"net"
"testing"
"github.com/docker/docker/libnetwork/types"
"github.com/moby/swarmkit/v2/api"
"github.com/moby/swarmkit/v2/manager/allocator/networkallocator"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func newNetworkAllocator(t *testing.T) networkallocator.NetworkAllocator {
na, err := (&Provider{}).NewAllocator(nil)
assert.Check(t, err)
assert.Check(t, na != nil)
return na
}
func TestNew(t *testing.T) {
newNetworkAllocator(t)
}
func TestAllocateInvalidIPAM(t *testing.T) {
na := newNetworkAllocator(t)
n := &api.Network{
ID: "testID",
Spec: api.NetworkSpec{
Annotations: api.Annotations{
Name: "test",
},
DriverConfig: &api.Driver{},
IPAM: &api.IPAMOptions{
Driver: &api.Driver{
Name: "invalidipam,",
},
},
},
}
err := na.Allocate(n)
assert.Check(t, is.ErrorContains(err, ""))
}
func TestAllocateInvalidDriver(t *testing.T) {
na := newNetworkAllocator(t)
n := &api.Network{
ID: "testID",
Spec: api.NetworkSpec{
Annotations: api.Annotations{
Name: "test",
},
DriverConfig: &api.Driver{
Name: "invaliddriver",
},
},
}
err := na.Allocate(n)
assert.Check(t, is.ErrorContains(err, ""))
}
func TestNetworkDoubleAllocate(t *testing.T) {
na := newNetworkAllocator(t)
n := &api.Network{
ID: "testID",
Spec: api.NetworkSpec{
Annotations: api.Annotations{
Name: "test",
},
},
}
err := na.Allocate(n)
assert.Check(t, err)
err = na.Allocate(n)
assert.Check(t, is.ErrorContains(err, ""))
}
func TestAllocateEmptyConfig(t *testing.T) {
na1 := newNetworkAllocator(t)
na2 := newNetworkAllocator(t)
n1 := &api.Network{
ID: "testID1",
Spec: api.NetworkSpec{
Annotations: api.Annotations{
Name: "test1",
},
},
}
n2 := &api.Network{
ID: "testID2",
Spec: api.NetworkSpec{
Annotations: api.Annotations{
Name: "test2",
},
},
}
err := na1.Allocate(n1)
assert.Check(t, err)
assert.Check(t, n1.IPAM.Configs != nil)
assert.Check(t, is.Equal(len(n1.IPAM.Configs), 1))
assert.Check(t, is.Equal(n1.IPAM.Configs[0].Range, ""))
assert.Check(t, is.Equal(len(n1.IPAM.Configs[0].Reserved), 0))
_, subnet11, err := net.ParseCIDR(n1.IPAM.Configs[0].Subnet)
assert.Check(t, err)
gwip11 := net.ParseIP(n1.IPAM.Configs[0].Gateway)
assert.Check(t, gwip11 != nil)
err = na1.Allocate(n2)
assert.Check(t, err)
assert.Check(t, n2.IPAM.Configs != nil)
assert.Check(t, is.Equal(len(n2.IPAM.Configs), 1))
assert.Check(t, is.Equal(n2.IPAM.Configs[0].Range, ""))
assert.Check(t, is.Equal(len(n2.IPAM.Configs[0].Reserved), 0))
_, subnet21, err := net.ParseCIDR(n2.IPAM.Configs[0].Subnet)
assert.Check(t, err)
gwip21 := net.ParseIP(n2.IPAM.Configs[0].Gateway)
assert.Check(t, gwip21 != nil)
// Allocate n1 ans n2 with another allocator instance but in
// intentionally reverse order.
err = na2.Allocate(n2)
assert.Check(t, err)
assert.Check(t, n2.IPAM.Configs != nil)
assert.Check(t, is.Equal(len(n2.IPAM.Configs), 1))
assert.Check(t, is.Equal(n2.IPAM.Configs[0].Range, ""))
assert.Check(t, is.Equal(len(n2.IPAM.Configs[0].Reserved), 0))
_, subnet22, err := net.ParseCIDR(n2.IPAM.Configs[0].Subnet)
assert.Check(t, err)
assert.Check(t, is.DeepEqual(subnet21, subnet22))
gwip22 := net.ParseIP(n2.IPAM.Configs[0].Gateway)
assert.Check(t, is.DeepEqual(gwip21, gwip22))
err = na2.Allocate(n1)
assert.Check(t, err)
assert.Check(t, n1.IPAM.Configs != nil)
assert.Check(t, is.Equal(len(n1.IPAM.Configs), 1))
assert.Check(t, is.Equal(n1.IPAM.Configs[0].Range, ""))
assert.Check(t, is.Equal(len(n1.IPAM.Configs[0].Reserved), 0))
_, subnet12, err := net.ParseCIDR(n1.IPAM.Configs[0].Subnet)
assert.Check(t, err)
assert.Check(t, is.DeepEqual(subnet11, subnet12))
gwip12 := net.ParseIP(n1.IPAM.Configs[0].Gateway)
assert.Check(t, is.DeepEqual(gwip11, gwip12))
}
func TestAllocateWithOneSubnet(t *testing.T) {
na := newNetworkAllocator(t)
n := &api.Network{
ID: "testID",
Spec: api.NetworkSpec{
Annotations: api.Annotations{
Name: "test",
},
DriverConfig: &api.Driver{},
IPAM: &api.IPAMOptions{
Driver: &api.Driver{},
Configs: []*api.IPAMConfig{
{
Subnet: "192.168.1.0/24",
},
},
},
},
}
err := na.Allocate(n)
assert.Check(t, err)
assert.Check(t, is.Equal(len(n.IPAM.Configs), 1))
assert.Check(t, is.Equal(n.IPAM.Configs[0].Range, ""))
assert.Check(t, is.Equal(len(n.IPAM.Configs[0].Reserved), 0))
assert.Check(t, is.Equal(n.IPAM.Configs[0].Subnet, "192.168.1.0/24"))
ip := net.ParseIP(n.IPAM.Configs[0].Gateway)
assert.Check(t, ip != nil)
}
func TestAllocateWithOneSubnetGateway(t *testing.T) {
na := newNetworkAllocator(t)
n := &api.Network{
ID: "testID",
Spec: api.NetworkSpec{
Annotations: api.Annotations{
Name: "test",
},
DriverConfig: &api.Driver{},
IPAM: &api.IPAMOptions{
Driver: &api.Driver{},
Configs: []*api.IPAMConfig{
{
Subnet: "192.168.1.0/24",
Gateway: "192.168.1.1",
},
},
},
},
}
err := na.Allocate(n)
assert.Check(t, err)
assert.Check(t, is.Equal(len(n.IPAM.Configs), 1))
assert.Check(t, is.Equal(n.IPAM.Configs[0].Range, ""))
assert.Check(t, is.Equal(len(n.IPAM.Configs[0].Reserved), 0))
assert.Check(t, is.Equal(n.IPAM.Configs[0].Subnet, "192.168.1.0/24"))
assert.Check(t, is.Equal(n.IPAM.Configs[0].Gateway, "192.168.1.1"))
}
func TestAllocateWithOneSubnetInvalidGateway(t *testing.T) {
na := newNetworkAllocator(t)
n := &api.Network{
ID: "testID",
Spec: api.NetworkSpec{
Annotations: api.Annotations{
Name: "test",
},
DriverConfig: &api.Driver{},
IPAM: &api.IPAMOptions{
Driver: &api.Driver{},
Configs: []*api.IPAMConfig{
{
Subnet: "192.168.1.0/24",
Gateway: "192.168.2.1",
},
},
},
},
}
err := na.Allocate(n)
assert.Check(t, is.ErrorContains(err, ""))
}
// TestAllocateWithSmallSubnet validates that /32 subnets don't produce an error,
// as /31 and /32 subnets are supported by docker daemon, starting with
// https://github.com/moby/moby/commit/3a938df4b570aad3bfb4d5342379582e872fc1a3,
func TestAllocateWithSmallSubnet(t *testing.T) {
na := newNetworkAllocator(t)
n := &api.Network{
ID: "testID",
Spec: api.NetworkSpec{
Annotations: api.Annotations{
Name: "test",
},
DriverConfig: &api.Driver{},
IPAM: &api.IPAMOptions{
Driver: &api.Driver{},
Configs: []*api.IPAMConfig{
{
Subnet: "1.1.1.1/32",
},
},
},
},
}
err := na.Allocate(n)
assert.Check(t, err)
}
func TestAllocateWithTwoSubnetsNoGateway(t *testing.T) {
na := newNetworkAllocator(t)
n := &api.Network{
ID: "testID",
Spec: api.NetworkSpec{
Annotations: api.Annotations{
Name: "test",
},
DriverConfig: &api.Driver{},
IPAM: &api.IPAMOptions{
Driver: &api.Driver{},
Configs: []*api.IPAMConfig{
{
Subnet: "192.168.1.0/24",
},
{
Subnet: "192.168.2.0/24",
},
},
},
},
}
err := na.Allocate(n)
assert.Check(t, err)
assert.Check(t, is.Equal(len(n.IPAM.Configs), 2))
assert.Check(t, is.Equal(n.IPAM.Configs[0].Range, ""))
assert.Check(t, is.Equal(len(n.IPAM.Configs[0].Reserved), 0))
assert.Check(t, is.Equal(n.IPAM.Configs[0].Subnet, "192.168.1.0/24"))
assert.Check(t, is.Equal(n.IPAM.Configs[1].Range, ""))
assert.Check(t, is.Equal(len(n.IPAM.Configs[1].Reserved), 0))
assert.Check(t, is.Equal(n.IPAM.Configs[1].Subnet, "192.168.2.0/24"))
ip := net.ParseIP(n.IPAM.Configs[0].Gateway)
assert.Check(t, ip != nil)
ip = net.ParseIP(n.IPAM.Configs[1].Gateway)
assert.Check(t, ip != nil)
}
func TestFree(t *testing.T) {
na := newNetworkAllocator(t)
n := &api.Network{
ID: "testID",
Spec: api.NetworkSpec{
Annotations: api.Annotations{
Name: "test",
},
DriverConfig: &api.Driver{},
IPAM: &api.IPAMOptions{
Driver: &api.Driver{},
Configs: []*api.IPAMConfig{
{
Subnet: "192.168.1.0/24",
Gateway: "192.168.1.1",
},
},
},
},
}
err := na.Allocate(n)
assert.Check(t, err)
err = na.Deallocate(n)
assert.Check(t, err)
// Reallocate again to make sure it succeeds.
err = na.Allocate(n)
assert.Check(t, err)
}
func TestAllocateTaskFree(t *testing.T) {
na1 := newNetworkAllocator(t)
na2 := newNetworkAllocator(t)
n1 := &api.Network{
ID: "testID1",
Spec: api.NetworkSpec{
Annotations: api.Annotations{
Name: "test1",
},
DriverConfig: &api.Driver{},
IPAM: &api.IPAMOptions{
Driver: &api.Driver{},
Configs: []*api.IPAMConfig{
{
Subnet: "192.168.1.0/24",
Gateway: "192.168.1.1",
},
},
},
},
}
n2 := &api.Network{
ID: "testID2",
Spec: api.NetworkSpec{
Annotations: api.Annotations{
Name: "test2",
},
DriverConfig: &api.Driver{},
IPAM: &api.IPAMOptions{
Driver: &api.Driver{},
Configs: []*api.IPAMConfig{
{
Subnet: "192.168.2.0/24",
Gateway: "192.168.2.1",
},
},
},
},
}
task1 := &api.Task{
Networks: []*api.NetworkAttachment{
{
Network: n1,
},
{
Network: n2,
},
},
}
task2 := &api.Task{
Networks: []*api.NetworkAttachment{
{
Network: n1,
},
{
Network: n2,
},
},
}
err := na1.Allocate(n1)
assert.Check(t, err)
err = na1.Allocate(n2)
assert.Check(t, err)
err = na1.AllocateTask(task1)
assert.Check(t, err)
assert.Check(t, is.Equal(len(task1.Networks[0].Addresses), 1))
assert.Check(t, is.Equal(len(task1.Networks[1].Addresses), 1))
_, subnet1, _ := net.ParseCIDR("192.168.1.0/24")
_, subnet2, _ := net.ParseCIDR("192.168.2.0/24")
// variable coding: network/task/allocator
ip111, _, err := net.ParseCIDR(task1.Networks[0].Addresses[0])
assert.Check(t, err)
ip211, _, err := net.ParseCIDR(task1.Networks[1].Addresses[0])
assert.Check(t, err)
assert.Check(t, is.Equal(subnet1.Contains(ip111), true))
assert.Check(t, is.Equal(subnet2.Contains(ip211), true))
err = na1.AllocateTask(task2)
assert.Check(t, err)
assert.Check(t, is.Equal(len(task2.Networks[0].Addresses), 1))
assert.Check(t, is.Equal(len(task2.Networks[1].Addresses), 1))
ip121, _, err := net.ParseCIDR(task2.Networks[0].Addresses[0])
assert.Check(t, err)
ip221, _, err := net.ParseCIDR(task2.Networks[1].Addresses[0])
assert.Check(t, err)
assert.Check(t, is.Equal(subnet1.Contains(ip121), true))
assert.Check(t, is.Equal(subnet2.Contains(ip221), true))
// Now allocate the same the same tasks in a second allocator
// but intentionally in reverse order.
err = na2.Allocate(n1)
assert.Check(t, err)
err = na2.Allocate(n2)
assert.Check(t, err)
err = na2.AllocateTask(task2)
assert.Check(t, err)
assert.Check(t, is.Equal(len(task2.Networks[0].Addresses), 1))
assert.Check(t, is.Equal(len(task2.Networks[1].Addresses), 1))
ip122, _, err := net.ParseCIDR(task2.Networks[0].Addresses[0])
assert.Check(t, err)
ip222, _, err := net.ParseCIDR(task2.Networks[1].Addresses[0])
assert.Check(t, err)
assert.Check(t, is.Equal(subnet1.Contains(ip122), true))
assert.Check(t, is.Equal(subnet2.Contains(ip222), true))
assert.Check(t, is.DeepEqual(ip121, ip122))
assert.Check(t, is.DeepEqual(ip221, ip222))
err = na2.AllocateTask(task1)
assert.Check(t, err)
assert.Check(t, is.Equal(len(task1.Networks[0].Addresses), 1))
assert.Check(t, is.Equal(len(task1.Networks[1].Addresses), 1))
ip112, _, err := net.ParseCIDR(task1.Networks[0].Addresses[0])
assert.Check(t, err)
ip212, _, err := net.ParseCIDR(task1.Networks[1].Addresses[0])
assert.Check(t, err)
assert.Check(t, is.Equal(subnet1.Contains(ip112), true))
assert.Check(t, is.Equal(subnet2.Contains(ip212), true))
assert.Check(t, is.DeepEqual(ip111, ip112))
assert.Check(t, is.DeepEqual(ip211, ip212))
// Deallocate task
err = na1.DeallocateTask(task1)
assert.Check(t, err)
assert.Check(t, is.Equal(len(task1.Networks[0].Addresses), 0))
assert.Check(t, is.Equal(len(task1.Networks[1].Addresses), 0))
// Try allocation after free
err = na1.AllocateTask(task1)
assert.Check(t, err)
assert.Check(t, is.Equal(len(task1.Networks[0].Addresses), 1))
assert.Check(t, is.Equal(len(task1.Networks[1].Addresses), 1))
ip111, _, err = net.ParseCIDR(task1.Networks[0].Addresses[0])
assert.Check(t, err)
ip211, _, err = net.ParseCIDR(task1.Networks[1].Addresses[0])
assert.Check(t, err)
assert.Check(t, is.Equal(subnet1.Contains(ip111), true))
assert.Check(t, is.Equal(subnet2.Contains(ip211), true))
err = na1.DeallocateTask(task1)
assert.Check(t, err)
assert.Check(t, is.Equal(len(task1.Networks[0].Addresses), 0))
assert.Check(t, is.Equal(len(task1.Networks[1].Addresses), 0))
// Try to free endpoints on an already freed task
err = na1.DeallocateTask(task1)
assert.Check(t, err)
}
func TestAllocateService(t *testing.T) {
na := newNetworkAllocator(t)
n := &api.Network{
ID: "testID",
Spec: api.NetworkSpec{
Annotations: api.Annotations{
Name: "test",
},
},
}
s := &api.Service{
ID: "testID1",
Spec: api.ServiceSpec{
Task: api.TaskSpec{
Networks: []*api.NetworkAttachmentConfig{
{
Target: "testID",
},
},
},
Endpoint: &api.EndpointSpec{
Ports: []*api.PortConfig{
{
Name: "http",
TargetPort: 80,
},
{
Name: "https",
TargetPort: 443,
},
},
},
},
}
err := na.Allocate(n)
assert.Check(t, err)
assert.Check(t, n.IPAM.Configs != nil)
assert.Check(t, is.Equal(len(n.IPAM.Configs), 1))
assert.Check(t, is.Equal(n.IPAM.Configs[0].Range, ""))
assert.Check(t, is.Equal(len(n.IPAM.Configs[0].Reserved), 0))
_, subnet, err := net.ParseCIDR(n.IPAM.Configs[0].Subnet)
assert.Check(t, err)
gwip := net.ParseIP(n.IPAM.Configs[0].Gateway)
assert.Check(t, gwip != nil)
err = na.AllocateService(s)
assert.Check(t, err)
assert.Check(t, is.Len(s.Endpoint.Ports, 0)) // Network allocator is not responsible for allocating ports.
assert.Check(t, is.Equal(1, len(s.Endpoint.VirtualIPs)))
assert.Check(t, is.DeepEqual(s.Endpoint.Spec, s.Spec.Endpoint))
ip, _, err := net.ParseCIDR(s.Endpoint.VirtualIPs[0].Addr)
assert.Check(t, err)
assert.Check(t, is.Equal(true, subnet.Contains(ip)))
}
func TestDeallocateServiceAllocateIngressMode(t *testing.T) {
na := newNetworkAllocator(t)
n := &api.Network{
ID: "testNetID1",
Spec: api.NetworkSpec{
Annotations: api.Annotations{
Name: "test",
},
Ingress: true,
},
}
err := na.Allocate(n)
assert.Check(t, err)
s := &api.Service{
ID: "testID1",
Spec: api.ServiceSpec{
Endpoint: &api.EndpointSpec{
Ports: []*api.PortConfig{
{
Name: "some_tcp",
TargetPort: 1234,
PublishedPort: 1234,
PublishMode: api.PublishModeIngress,
},
},
},
},
Endpoint: &api.Endpoint{},
}
s.Endpoint.VirtualIPs = append(s.Endpoint.VirtualIPs,
&api.Endpoint_VirtualIP{NetworkID: n.ID})
err = na.AllocateService(s)
assert.Check(t, err)
assert.Check(t, is.Len(s.Endpoint.VirtualIPs, 1))
err = na.DeallocateService(s)
assert.Check(t, err)
assert.Check(t, is.Len(s.Endpoint.Ports, 0))
assert.Check(t, is.Len(s.Endpoint.VirtualIPs, 0))
// Allocate again.
s.Endpoint.VirtualIPs = append(s.Endpoint.VirtualIPs,
&api.Endpoint_VirtualIP{NetworkID: n.ID})
err = na.AllocateService(s)
assert.Check(t, err)
assert.Check(t, is.Len(s.Endpoint.VirtualIPs, 1))
}
func TestServiceNetworkUpdate(t *testing.T) {
na := newNetworkAllocator(t)
n1 := &api.Network{
ID: "testID1",
Spec: api.NetworkSpec{
Annotations: api.Annotations{
Name: "test",
},
},
}
n2 := &api.Network{
ID: "testID2",
Spec: api.NetworkSpec{
Annotations: api.Annotations{
Name: "test2",
},
},
}
// Allocate both networks
err := na.Allocate(n1)
assert.Check(t, err)
err = na.Allocate(n2)
assert.Check(t, err)
// Attach a network to a service spec nd allocate a service
s := &api.Service{
ID: "testID1",
Spec: api.ServiceSpec{
Task: api.TaskSpec{
Networks: []*api.NetworkAttachmentConfig{
{
Target: "testID1",
},
},
},
Endpoint: &api.EndpointSpec{
Mode: api.ResolutionModeVirtualIP,
},
},
}
err = na.AllocateService(s)
assert.Check(t, err)
assert.Check(t, na.IsServiceAllocated(s))
assert.Check(t, is.Len(s.Endpoint.VirtualIPs, 1))
// Now update the same service with another network
s.Spec.Task.Networks = append(s.Spec.Task.Networks, &api.NetworkAttachmentConfig{Target: "testID2"})
assert.Check(t, !na.IsServiceAllocated(s))
err = na.AllocateService(s)
assert.Check(t, err)
assert.Check(t, na.IsServiceAllocated(s))
assert.Check(t, is.Len(s.Endpoint.VirtualIPs, 2))
s.Spec.Task.Networks = s.Spec.Task.Networks[:1]
// Check if service needs update and allocate with updated service spec
assert.Check(t, !na.IsServiceAllocated(s))
err = na.AllocateService(s)
assert.Check(t, err)
assert.Check(t, na.IsServiceAllocated(s))
assert.Check(t, is.Len(s.Endpoint.VirtualIPs, 1))
s.Spec.Task.Networks = s.Spec.Task.Networks[:0]
// Check if service needs update with all the networks removed and allocate with updated service spec
assert.Check(t, !na.IsServiceAllocated(s))
err = na.AllocateService(s)
assert.Check(t, err)
assert.Check(t, na.IsServiceAllocated(s))
assert.Check(t, is.Len(s.Endpoint.VirtualIPs, 0))
// Attach a network and allocate service
s.Spec.Task.Networks = append(s.Spec.Task.Networks, &api.NetworkAttachmentConfig{Target: "testID2"})
assert.Check(t, !na.IsServiceAllocated(s))
err = na.AllocateService(s)
assert.Check(t, err)
assert.Check(t, na.IsServiceAllocated(s))
assert.Check(t, is.Len(s.Endpoint.VirtualIPs, 1))
}
type mockIpam struct {
actualIpamOptions map[string]string
}
func (a *mockIpam) GetDefaultAddressSpaces() (string, string, error) {
return "defaultAS", "defaultAS", nil
}
func (a *mockIpam) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) {
a.actualIpamOptions = options
poolCidr, _ := types.ParseCIDR(pool)
return fmt.Sprintf("%s/%s", "defaultAS", pool), poolCidr, nil, nil
}
func (a *mockIpam) ReleasePool(poolID string) error {
return nil
}
func (a *mockIpam) RequestAddress(poolID string, ip net.IP, opts map[string]string) (*net.IPNet, map[string]string, error) {
return nil, nil, nil
}
func (a *mockIpam) ReleaseAddress(poolID string, ip net.IP) error {
return nil
}
func (a *mockIpam) IsBuiltIn() bool {
return true
}
func TestCorrectlyPassIPAMOptions(t *testing.T) {
var err error
expectedIpamOptions := map[string]string{"network-name": "freddie"}
na := newNetworkAllocator(t)
ipamDriver := &mockIpam{}
err = na.(*cnmNetworkAllocator).ipamRegistry.RegisterIpamDriver("mockipam", ipamDriver)
assert.Check(t, err)
n := &api.Network{
ID: "testID",
Spec: api.NetworkSpec{
Annotations: api.Annotations{
Name: "test",
},
DriverConfig: &api.Driver{},
IPAM: &api.IPAMOptions{
Driver: &api.Driver{
Name: "mockipam",
Options: expectedIpamOptions,
},
Configs: []*api.IPAMConfig{
{
Subnet: "192.168.1.0/24",
Gateway: "192.168.1.1",
},
},
},
},
}
err = na.Allocate(n)
assert.Check(t, is.DeepEqual(expectedIpamOptions, ipamDriver.actualIpamOptions))
assert.Check(t, err)
}

View file

@ -0,0 +1,91 @@
package cnmallocator
import (
"strings"
"github.com/docker/docker/libnetwork/driverapi"
"github.com/docker/docker/libnetwork/drivers/overlay/overlayutils"
"github.com/docker/docker/libnetwork/ipamapi"
"github.com/docker/docker/pkg/plugingetter"
"github.com/moby/swarmkit/v2/api"
"github.com/moby/swarmkit/v2/manager/allocator/networkallocator"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type Provider struct {
pg plugingetter.PluginGetter
}
var _ networkallocator.Provider = &Provider{}
// NewProvider returns a new cnmallocator provider.
func NewProvider(pg plugingetter.PluginGetter) *Provider {
return &Provider{pg: pg}
}
// ValidateIPAMDriver implements networkallocator.NetworkProvider.
func (p *Provider) ValidateIPAMDriver(driver *api.Driver) error {
if driver == nil {
// It is ok to not specify the driver. We will choose
// a default driver.
return nil
}
if driver.Name == "" {
return status.Errorf(codes.InvalidArgument, "driver name: if driver is specified name is required")
}
if strings.ToLower(driver.Name) == ipamapi.DefaultIPAM {
return nil
}
return p.validatePluginDriver(driver, ipamapi.PluginEndpointType)
}
// ValidateIngressNetworkDriver implements networkallocator.NetworkProvider.
func (p *Provider) ValidateIngressNetworkDriver(driver *api.Driver) error {
if driver != nil && driver.Name != "overlay" {
return status.Errorf(codes.Unimplemented, "only overlay driver is currently supported for ingress network")
}
return p.ValidateNetworkDriver(driver)
}
// ValidateNetworkDriver implements networkallocator.NetworkProvider.
func (p *Provider) ValidateNetworkDriver(driver *api.Driver) error {
if driver == nil {
// It is ok to not specify the driver. We will choose
// a default driver.
return nil
}
if driver.Name == "" {
return status.Errorf(codes.InvalidArgument, "driver name: if driver is specified name is required")
}
// First check against the known drivers
if IsBuiltInDriver(driver.Name) {
return nil
}
return p.validatePluginDriver(driver, driverapi.NetworkPluginEndpointType)
}
func (p *Provider) validatePluginDriver(driver *api.Driver, pluginType string) error {
if p.pg == nil {
return status.Errorf(codes.InvalidArgument, "plugin %s not supported", driver.Name)
}
plug, err := p.pg.Get(driver.Name, pluginType, plugingetter.Lookup)
if err != nil {
return status.Errorf(codes.InvalidArgument, "error during lookup of plugin %s", driver.Name)
}
if plug.IsV1() {
return status.Errorf(codes.InvalidArgument, "legacy plugin %s of type %s is not supported in swarm mode", driver.Name, pluginType)
}
return nil
}
func (p *Provider) SetDefaultVXLANUDPPort(port uint32) error {
return overlayutils.ConfigVXLANUDPPort(port)
}

View file

@ -0,0 +1,31 @@
package cnmallocator
import (
"testing"
"github.com/moby/swarmkit/v2/api"
"github.com/moby/swarmkit/v2/testutils"
"google.golang.org/grpc/codes"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestValidateDriver(t *testing.T) {
p := NewProvider(nil)
for _, tt := range []struct {
name string
validator func(*api.Driver) error
}{
{"IPAM", p.ValidateIPAMDriver},
{"Network", p.ValidateNetworkDriver},
} {
t.Run(tt.name, func(t *testing.T) {
assert.Check(t, tt.validator(nil))
err := tt.validator(&api.Driver{Name: ""})
assert.Check(t, is.ErrorContains(err, ""))
assert.Check(t, is.Equal(codes.InvalidArgument, testutils.ErrorCode(err)))
})
}
}

View file

@ -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)

View file

@ -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 {

View file

@ -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)

View file

@ -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 {

View file

@ -67,7 +67,7 @@ func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo,
veth, err := nlh.LinkByName(overlayIfName)
if err != nil {
return fmt.Errorf("cound not find link by name %s: %v", overlayIfName, err)
return fmt.Errorf("could not find link by name %s: %v", overlayIfName, err)
}
err = nlh.LinkSetMTU(veth, mtu)
if err != nil {

View file

@ -406,7 +406,7 @@ func parseStaticRoutes(r api.JoinResponse) ([]*types.StaticRoute, error) {
return routes, nil
}
// parseInterfaces validates all the parameters of an Interface and returns them.
// parseInterface validates all the parameters of an Interface and returns them.
func parseInterface(r api.CreateEndpointResponse) (*api.Interface, error) {
var outIf *api.Interface

View file

@ -11,6 +11,7 @@ import (
"sync"
"github.com/containerd/log"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/internal/sliceutil"
"github.com/docker/docker/libnetwork/datastore"
"github.com/docker/docker/libnetwork/ipamapi"
@ -543,6 +544,10 @@ func (ep *Endpoint) sbJoin(sb *Sandbox, options ...EndpointOption) (err error) {
return err
}
if err = addEpToResolver(context.TODO(), n.Name(), ep.Name(), &sb.config, ep.iface, n.Resolvers()); err != nil {
return errdefs.System(err)
}
if err = n.getController().updateToStore(ep); err != nil {
return err
}
@ -569,12 +574,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 {
@ -745,6 +750,10 @@ func (ep *Endpoint) sbLeave(sb *Sandbox, force bool) error {
log.G(context.TODO()).Warnf("Failed to clean up service info on container %s disconnect: %v", ep.name, err)
}
if err := deleteEpFromResolver(ep.Name(), ep.iface, n.Resolvers()); err != nil {
log.G(context.TODO()).Warnf("Failed to clean up resolver info on container %s disconnect: %v", ep.name, err)
}
if err := sb.clearNetworkResources(ep); err != nil {
log.G(context.TODO()).Warnf("Failed to clean up network resources on container %s disconnect: %v", ep.name, err)
}
@ -767,13 +776,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()

View file

@ -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 {

View file

@ -51,3 +51,11 @@ func SubnetRange(network, subnet netip.Prefix) (start, end uint64) {
end = start + (1 << uint64(subnet.Addr().BitLen()-subnet.Bits())) - 1
return start, end
}
// AddrPortFromNet converts a net.Addr into a netip.AddrPort.
func AddrPortFromNet(addr net.Addr) netip.AddrPort {
if a, ok := addr.(interface{ AddrPort() netip.AddrPort }); ok {
return a.AddrPort()
}
return netip.AddrPort{}
}

View file

@ -192,7 +192,6 @@ type Network struct {
dbExists bool
persist bool
drvOnce *sync.Once
resolverOnce sync.Once //nolint:nolintlint,unused // only used on windows
resolver []*Resolver
internal bool
attachable bool
@ -204,6 +203,7 @@ type Network struct {
configFrom string
loadBalancerIP net.IP
loadBalancerMode string
platformNetwork //nolint:nolintlint,unused // only populated on windows
mu sync.Mutex
}
@ -244,6 +244,13 @@ func (n *Network) Type() string {
return n.networkType
}
func (n *Network) Resolvers() []*Resolver {
n.mu.Lock()
defer n.mu.Unlock()
return n.resolver
}
func (n *Network) Key() []string {
n.mu.Lock()
defer n.mu.Unlock()
@ -2097,10 +2104,6 @@ func (n *Network) ResolveService(ctx context.Context, name string) ([]*net.SRV,
return srv, ip
}
func (n *Network) ExecFunc(f func()) error {
return types.NotImplementedErrorf("ExecFunc not supported by network")
}
func (n *Network) NdotsSet() bool {
return false
}

View file

@ -2,13 +2,33 @@
package libnetwork
import "github.com/docker/docker/libnetwork/ipamapi"
import (
"context"
"github.com/docker/docker/libnetwork/ipamapi"
)
type platformNetwork struct{} //nolint:nolintlint,unused // only populated on windows
// Stub implementations for DNS related functions
func (n *Network) startResolver() {
}
func addEpToResolver(
ctx context.Context,
netName, epName string,
config *containerConfig,
epIface *EndpointInterface,
resolvers []*Resolver,
) error {
return nil
}
func deleteEpFromResolver(epName string, epIface *EndpointInterface, resolvers []*Resolver) error {
return nil
}
func defaultIpamForNetworkType(networkType string) string {
return ipamapi.DefaultIPAM
}

View file

@ -4,7 +4,12 @@ package libnetwork
import (
"context"
"fmt"
"net"
"net/netip"
"runtime"
"strings"
"sync"
"time"
"github.com/Microsoft/hcsshim"
@ -12,8 +17,14 @@ import (
"github.com/docker/docker/libnetwork/drivers/windows"
"github.com/docker/docker/libnetwork/ipamapi"
"github.com/docker/docker/libnetwork/ipams/windowsipam"
"github.com/pkg/errors"
)
type platformNetwork struct {
resolverOnce sync.Once
dnsCompartment uint32
}
func executeInCompartment(compartmentID uint32, x func()) {
runtime.LockOSThread()
@ -28,6 +39,11 @@ func executeInCompartment(compartmentID uint32, x func()) {
x()
}
func (n *Network) ExecFunc(f func()) error {
executeInCompartment(n.dnsCompartment, f)
return nil
}
func (n *Network) startResolver() {
if n.networkType == "ics" {
return
@ -48,9 +64,10 @@ func (n *Network) startResolver() {
for _, subnet := range hnsresponse.Subnets {
if subnet.GatewayAddress != "" {
for i := 0; i < 3; i++ {
resolver := NewResolver(subnet.GatewayAddress, false, n)
resolver := NewResolver(subnet.GatewayAddress, true, n)
log.G(context.TODO()).Debugf("Binding a resolver on network %s gateway %s", n.Name(), subnet.GatewayAddress)
executeInCompartment(hnsresponse.DNSServerCompartment, resolver.SetupFunc(53))
n.dnsCompartment = hnsresponse.DNSServerCompartment
n.ExecFunc(resolver.SetupFunc(53))
if err = resolver.Start(); err != nil {
log.G(context.TODO()).Errorf("Resolver Setup/Start failed for container %s, %q", n.Name(), err)
@ -66,6 +83,166 @@ func (n *Network) startResolver() {
})
}
// addEpToResolver configures the internal DNS resolver for an endpoint.
//
// Windows resolvers don't consistently fall back to a secondary server if they
// get a SERVFAIL from our resolver. So, our resolver needs to forward the query
// upstream.
//
// To retrieve the list of DNS Servers to use for requests originating from an
// endpoint, this method finds the HNSEndpoint represented by the endpoint. If
// HNSEndpoint's list of DNS servers includes the HNSEndpoint's gateway address,
// it's the Resolver running at that address. Other DNS servers in the
// list have either come from config ('--dns') or have been set up by HNS as
// external resolvers, these are the external servers the Resolver should
// use for DNS requests from that endpoint.
func addEpToResolver(
ctx context.Context,
netName, epName string,
config *containerConfig,
epIface *EndpointInterface,
resolvers []*Resolver,
) error {
if config.dnsNoProxy {
return nil
}
hnsEndpoints, err := hcsshim.HNSListEndpointRequest()
if err != nil {
return nil
}
return addEpToResolverImpl(ctx, netName, epName, epIface, resolvers, hnsEndpoints)
}
func addEpToResolverImpl(
ctx context.Context,
netName, epName string,
epIface *EndpointInterface,
resolvers []*Resolver,
hnsEndpoints []hcsshim.HNSEndpoint,
) error {
// Find the HNSEndpoint represented by ep, matching on endpoint address.
hnsEp := findHNSEp(epIface.addr, epIface.addrv6, hnsEndpoints)
if hnsEp == nil || !hnsEp.EnableInternalDNS {
return nil
}
// Find the resolver for that HNSEndpoint, matching on gateway address.
resolver := findResolver(resolvers, hnsEp.GatewayAddress, hnsEp.GatewayAddressV6)
if resolver == nil {
log.G(ctx).WithFields(log.Fields{
"network": netName,
"endpoint": epName,
}).Debug("No internal DNS resolver to configure")
return nil
}
// Get the list of DNS servers HNS has set up for this Endpoint.
var dnsList []extDNSEntry
dnsServers := strings.Split(hnsEp.DNSServerList, ",")
// Create an extDNSEntry for each DNS server, apart from 'resolver' itself.
var foundSelf bool
hnsGw4, _ := netip.ParseAddr(hnsEp.GatewayAddress)
hnsGw6, _ := netip.ParseAddr(hnsEp.GatewayAddressV6)
for _, dnsServer := range dnsServers {
dnsAddr, _ := netip.ParseAddr(dnsServer)
if dnsAddr.IsValid() && (dnsAddr == hnsGw4 || dnsAddr == hnsGw6) {
foundSelf = true
} else {
dnsList = append(dnsList, extDNSEntry{IPStr: dnsServer})
}
}
if !foundSelf {
log.G(ctx).WithFields(log.Fields{
"network": netName,
"endpoint": epName,
}).Debug("Endpoint is not configured to use internal DNS resolver")
return nil
}
// If the internal resolver is configured as one of this endpoint's DNS servers,
// tell it which ext servers to use for requests from this endpoint's addresses.
log.G(ctx).Infof("External DNS servers for '%s': %v", epName, dnsList)
if srcAddr, ok := netip.AddrFromSlice(hnsEp.IPAddress); ok {
if err := resolver.SetExtServersForSrc(srcAddr.Unmap(), dnsList); err != nil {
return errors.Wrapf(err, "failed to set external DNS servers for %s address %s",
epName, hnsEp.IPAddress)
}
}
if srcAddr, ok := netip.AddrFromSlice(hnsEp.IPv6Address); ok {
if err := resolver.SetExtServersForSrc(srcAddr, dnsList); err != nil {
return errors.Wrapf(err, "failed to set external DNS servers for %s address %s",
epName, hnsEp.IPv6Address)
}
}
return nil
}
func deleteEpFromResolver(epName string, epIface *EndpointInterface, resolvers []*Resolver) error {
hnsEndpoints, err := hcsshim.HNSListEndpointRequest()
if err != nil {
return nil
}
return deleteEpFromResolverImpl(epName, epIface, resolvers, hnsEndpoints)
}
func deleteEpFromResolverImpl(
epName string,
epIface *EndpointInterface,
resolvers []*Resolver,
hnsEndpoints []hcsshim.HNSEndpoint,
) error {
// Find the HNSEndpoint represented by ep, matching on endpoint address.
hnsEp := findHNSEp(epIface.addr, epIface.addrv6, hnsEndpoints)
if hnsEp == nil {
return fmt.Errorf("no HNS endpoint for %s", epName)
}
// Find the resolver for that HNSEndpoint, matching on gateway address.
resolver := findResolver(resolvers, hnsEp.GatewayAddress, hnsEp.GatewayAddressV6)
if resolver == nil {
return nil
}
// Delete external DNS servers for the endpoint's IP addresses.
if srcAddr, ok := netip.AddrFromSlice(hnsEp.IPAddress); ok {
if err := resolver.SetExtServersForSrc(srcAddr.Unmap(), nil); err != nil {
return errors.Wrapf(err, "failed to delete external DNS servers for %s address %s",
epName, hnsEp.IPv6Address)
}
}
if srcAddr, ok := netip.AddrFromSlice(hnsEp.IPv6Address); ok {
if err := resolver.SetExtServersForSrc(srcAddr, nil); err != nil {
return errors.Wrapf(err, "failed to delete external DNS servers for %s address %s",
epName, hnsEp.IPv6Address)
}
}
return nil
}
func findHNSEp(ip4, ip6 *net.IPNet, hnsEndpoints []hcsshim.HNSEndpoint) *hcsshim.HNSEndpoint {
for _, hnsEp := range hnsEndpoints {
if (hnsEp.IPAddress != nil && hnsEp.IPAddress.Equal(ip4.IP)) ||
(hnsEp.IPv6Address != nil && hnsEp.IPv6Address.Equal(ip6.IP)) {
return &hnsEp
}
}
return nil
}
func findResolver(resolvers []*Resolver, gw4, gw6 string) *Resolver {
gw4addr, _ := netip.ParseAddr(gw4)
gw6addr, _ := netip.ParseAddr(gw6)
for _, resolver := range resolvers {
ns := resolver.NameServer()
if ns.IsValid() && (ns == gw4addr || ns == gw6addr) {
return resolver
}
}
return nil
}
func defaultIpamForNetworkType(networkType string) string {
if windows.IsBuiltinLocalDriver(networkType) {
return windowsipam.DefaultIPAM

View file

@ -0,0 +1,201 @@
package libnetwork
import (
"context"
"fmt"
"net"
"net/netip"
"testing"
"github.com/Microsoft/hcsshim"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestAddEpToResolver(t *testing.T) {
const (
ep1v4 = "192.0.2.11"
ep2v4 = "192.0.2.12"
epFiveDNS = "192.0.2.13"
epNoIntDNS = "192.0.2.14"
ep1v6 = "2001:db8:aaaa::2"
gw1v4 = "192.0.2.1"
gw2v4 = "192.0.2.2"
gw1v6 = "2001:db8:aaaa::1"
dns1v4 = "198.51.100.1"
dns2v4 = "198.51.100.2"
dns3v4 = "198.51.100.3"
)
hnsEndpoints := map[string]hcsshim.HNSEndpoint{
ep1v4: {
IPAddress: net.ParseIP(ep1v4),
GatewayAddress: gw1v4,
DNSServerList: gw1v4 + "," + dns1v4,
EnableInternalDNS: true,
},
ep2v4: {
IPAddress: net.ParseIP(ep2v4),
GatewayAddress: gw1v4,
DNSServerList: gw1v4 + "," + dns2v4,
EnableInternalDNS: true,
},
epFiveDNS: {
IPAddress: net.ParseIP(epFiveDNS),
GatewayAddress: gw1v4,
DNSServerList: gw1v4 + "," + dns1v4 + "," + dns2v4 + "," + dns3v4 + ",198.51.100.4",
EnableInternalDNS: true,
},
epNoIntDNS: {
IPAddress: net.ParseIP(epNoIntDNS),
GatewayAddress: gw1v4,
DNSServerList: gw1v4 + "," + dns1v4,
//EnableInternalDNS: false,
},
ep1v6: {
IPv6Address: net.ParseIP(ep1v6),
GatewayAddressV6: gw1v6,
DNSServerList: gw1v6 + "," + dns1v4,
EnableInternalDNS: true,
},
}
makeIPNet := func(addr, netmask string) *net.IPNet {
t.Helper()
ip, ipnet, err := net.ParseCIDR(addr + "/" + netmask)
assert.NilError(t, err)
return &net.IPNet{IP: ip, Mask: ipnet.Mask}
}
testcases := []struct {
name string
epToAdd *EndpointInterface
hnsEndpoints []hcsshim.HNSEndpoint
resolverLAs []string
expIPToExtDNS map[netip.Addr][maxExtDNS]extDNSEntry
expResolverIdx int
}{
{
name: "ipv4",
epToAdd: &EndpointInterface{
addr: makeIPNet(ep1v4, "32"),
},
hnsEndpoints: []hcsshim.HNSEndpoint{
hnsEndpoints[ep1v4],
},
resolverLAs: []string{gw1v4},
expIPToExtDNS: map[netip.Addr][maxExtDNS]extDNSEntry{
netip.MustParseAddr(ep1v4): {{IPStr: dns1v4}},
},
},
{
name: "limit of three dns servers",
epToAdd: &EndpointInterface{
addr: makeIPNet(epFiveDNS, "32"),
},
hnsEndpoints: []hcsshim.HNSEndpoint{
hnsEndpoints[epFiveDNS],
},
resolverLAs: []string{gw1v4},
// Expect the internal resolver to keep the first three ext-servers.
expIPToExtDNS: map[netip.Addr][maxExtDNS]extDNSEntry{
netip.MustParseAddr(epFiveDNS): {
{IPStr: dns1v4},
{IPStr: dns2v4},
{IPStr: dns3v4},
},
},
},
{
name: "disabled internal resolver",
epToAdd: &EndpointInterface{
addr: makeIPNet(epNoIntDNS, "32"),
},
hnsEndpoints: []hcsshim.HNSEndpoint{
hnsEndpoints[epNoIntDNS],
hnsEndpoints[ep2v4],
},
resolverLAs: []string{gw1v4},
},
{
name: "missing internal resolver",
epToAdd: &EndpointInterface{
addr: makeIPNet(ep1v4, "32"),
},
hnsEndpoints: []hcsshim.HNSEndpoint{
hnsEndpoints[ep1v4],
},
// The only resolver is for the gateway on a different network.
resolverLAs: []string{gw2v4},
},
{
name: "multiple resolvers and endpoints",
epToAdd: &EndpointInterface{
addr: makeIPNet(ep2v4, "32"),
},
hnsEndpoints: []hcsshim.HNSEndpoint{
hnsEndpoints[ep1v4],
hnsEndpoints[ep2v4],
},
// Put the internal resolver for this network second in the list.
expResolverIdx: 1,
resolverLAs: []string{gw2v4, gw1v4},
expIPToExtDNS: map[netip.Addr][maxExtDNS]extDNSEntry{
netip.MustParseAddr(ep2v4): {{IPStr: dns2v4}},
},
},
{
name: "ipv6",
epToAdd: &EndpointInterface{
addrv6: makeIPNet(ep1v6, "80"),
},
hnsEndpoints: []hcsshim.HNSEndpoint{
hnsEndpoints[ep1v6],
},
resolverLAs: []string{gw1v6},
expIPToExtDNS: map[netip.Addr][maxExtDNS]extDNSEntry{
netip.MustParseAddr(ep1v6): {{IPStr: dns1v4}},
},
},
}
eMapCmpOpts := []cmp.Option{
cmpopts.EquateEmpty(),
cmpopts.EquateComparable(netip.Addr{}),
cmpopts.IgnoreUnexported(extDNSEntry{}),
}
emptyEMap := map[netip.Addr][maxExtDNS]extDNSEntry{}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
// Set up resolvers with the required listen-addresses.
var resolvers []*Resolver
for _, la := range tc.resolverLAs {
resolvers = append(resolvers, NewResolver(la, true, nil))
}
// Add the endpoint and check expected results.
err := addEpToResolverImpl(context.TODO(),
"netname", "epname", tc.epToAdd, resolvers, tc.hnsEndpoints)
assert.Check(t, err)
for i, resolver := range resolvers {
if i == tc.expResolverIdx {
assert.Check(t, is.DeepEqual(resolver.ipToExtDNS.eMap, tc.expIPToExtDNS,
eMapCmpOpts...), fmt.Sprintf("resolveridx=%d", i))
} else {
assert.Check(t, is.DeepEqual(resolver.ipToExtDNS.eMap, emptyEMap,
eMapCmpOpts...), fmt.Sprintf("resolveridx=%d", i))
}
}
// Delete the endpoint, check nothing got left behind.
err = deleteEpFromResolverImpl("epname", tc.epToAdd, resolvers, tc.hnsEndpoints)
assert.Check(t, err)
for i, resolver := range resolvers {
assert.Check(t, is.DeepEqual(resolver.ipToExtDNS.eMap, emptyEMap,
eMapCmpOpts...), fmt.Sprintf("resolveridx=%d", i))
}
})
}
}

View file

@ -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 {

View file

@ -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 {

View file

@ -6,6 +6,7 @@ import (
"fmt"
"math/rand"
"net"
"net/netip"
"strconv"
"strings"
"sync"
@ -13,6 +14,7 @@ import (
"time"
"github.com/containerd/log"
"github.com/docker/docker/libnetwork/internal/netiputil"
"github.com/docker/docker/libnetwork/types"
"github.com/miekg/dns"
"go.opentelemetry.io/otel"
@ -65,17 +67,25 @@ type extDNSEntry struct {
HostLoopback bool
}
func (e extDNSEntry) String() string {
if e.HostLoopback {
return "host(" + e.IPStr + ")"
}
return e.IPStr
}
// Resolver is the embedded DNS server in Docker. It operates by listening on
// the container's loopback interface for DNS queries.
type Resolver struct {
backend DNSBackend
extDNSList [maxExtDNS]extDNSEntry
extDNSList [maxExtDNS]extDNSEntry // Ext servers to use when there's no entry in ipToExtDNS.
ipToExtDNS addrToExtDNSMap // DNS query source IP -> ext servers.
server *dns.Server
conn *net.UDPConn
tcpServer *dns.Server
tcpListen *net.TCPListener
err error
listenAddress string
listenAddress netip.Addr
proxyDNS atomic.Bool
startCh chan struct{}
logger *log.Entry
@ -87,18 +97,45 @@ type Resolver struct {
// NewResolver creates a new instance of the Resolver
func NewResolver(address string, proxyDNS bool, backend DNSBackend) *Resolver {
r := &Resolver{
backend: backend,
listenAddress: address,
err: fmt.Errorf("setup not done yet"),
startCh: make(chan struct{}, 1),
fwdSem: semaphore.NewWeighted(maxConcurrent),
logInverval: rate.Sometimes{Interval: logInterval},
backend: backend,
err: fmt.Errorf("setup not done yet"),
startCh: make(chan struct{}, 1),
fwdSem: semaphore.NewWeighted(maxConcurrent),
logInverval: rate.Sometimes{Interval: logInterval},
}
r.listenAddress, _ = netip.ParseAddr(address)
r.proxyDNS.Store(proxyDNS)
return r
}
type addrToExtDNSMap struct {
mu sync.Mutex
eMap map[netip.Addr][maxExtDNS]extDNSEntry
}
func (am *addrToExtDNSMap) get(addr netip.Addr) ([maxExtDNS]extDNSEntry, bool) {
am.mu.Lock()
defer am.mu.Unlock()
entries, ok := am.eMap[addr]
return entries, ok
}
func (am *addrToExtDNSMap) set(addr netip.Addr, entries []extDNSEntry) {
var e [maxExtDNS]extDNSEntry
copy(e[:], entries)
am.mu.Lock()
defer am.mu.Unlock()
if len(entries) > 0 {
if am.eMap == nil {
am.eMap = map[netip.Addr][maxExtDNS]extDNSEntry{}
}
am.eMap[addr] = e
} else {
delete(am.eMap, addr)
}
}
func (r *Resolver) log(ctx context.Context) *log.Entry {
if r.logger == nil {
return log.G(ctx)
@ -108,25 +145,23 @@ func (r *Resolver) log(ctx context.Context) *log.Entry {
// SetupFunc returns the setup function that should be run in the container's
// network namespace.
func (r *Resolver) SetupFunc(port int) func() {
func (r *Resolver) SetupFunc(port uint16) func() {
return func() {
var err error
// DNS operates primarily on UDP
r.conn, err = net.ListenUDP("udp", &net.UDPAddr{
IP: net.ParseIP(r.listenAddress),
Port: port,
})
r.conn, err = net.ListenUDP("udp", net.UDPAddrFromAddrPort(
netip.AddrPortFrom(r.listenAddress, port)),
)
if err != nil {
r.err = fmt.Errorf("error in opening name server socket %v", err)
return
}
// Listen on a TCP as well
r.tcpListen, err = net.ListenTCP("tcp", &net.TCPAddr{
IP: net.ParseIP(r.listenAddress),
Port: port,
})
r.tcpListen, err = net.ListenTCP("tcp", net.TCPAddrFromAddrPort(
netip.AddrPortFrom(r.listenAddress, port)),
)
if err != nil {
r.err = fmt.Errorf("error in opening name TCP server socket %v", err)
return
@ -186,7 +221,8 @@ func (r *Resolver) Stop() {
}
// SetExtServers configures the external nameservers the resolver should use
// when forwarding queries.
// when forwarding queries, unless SetExtServersForSrc has configured servers
// for the DNS client making the request.
func (r *Resolver) SetExtServers(extDNS []extDNSEntry) {
l := len(extDNS)
if l > maxExtDNS {
@ -203,8 +239,17 @@ func (r *Resolver) SetForwardingPolicy(policy bool) {
r.proxyDNS.Store(policy)
}
// SetExtServersForSrc configures the external nameservers the resolver should
// use when forwarding queries from srcAddr. If set, these servers will be used
// in preference to servers set by SetExtServers. Supplying a nil or empty extDNS
// deletes nameservers for srcAddr.
func (r *Resolver) SetExtServersForSrc(srcAddr netip.Addr, extDNS []extDNSEntry) error {
r.ipToExtDNS.set(srcAddr, extDNS)
return nil
}
// NameServer returns the IP of the DNS resolver for the containers.
func (r *Resolver) NameServer() string {
func (r *Resolver) NameServer() netip.Addr {
return r.listenAddress
}
@ -439,7 +484,7 @@ func (r *Resolver) serveDNS(w dns.ResponseWriter, query *dns.Msg) {
!strings.Contains(strings.TrimSuffix(queryName, "."), ".") {
resp = createRespMsg(query)
} else {
resp = r.forwardExtDNS(ctx, w.LocalAddr().Network(), query)
resp = r.forwardExtDNS(ctx, w.LocalAddr().Network(), w.RemoteAddr(), query)
}
}
@ -481,11 +526,11 @@ func (r *Resolver) dialExtDNS(proto string, server extDNSEntry) (net.Conn, error
return extConn, nil
}
func (r *Resolver) forwardExtDNS(ctx context.Context, proto string, query *dns.Msg) *dns.Msg {
func (r *Resolver) forwardExtDNS(ctx context.Context, proto string, remoteAddr net.Addr, query *dns.Msg) *dns.Msg {
ctx, span := otel.Tracer("").Start(ctx, "resolver.forwardExtDNS")
defer span.End()
for _, extDNS := range r.extDNSList {
for _, extDNS := range r.extDNS(netiputil.AddrPortFromNet(remoteAddr)) {
if extDNS.IPStr == "" {
break
}
@ -548,6 +593,13 @@ func (r *Resolver) forwardExtDNS(ctx context.Context, proto string, query *dns.M
return nil
}
func (r *Resolver) extDNS(remoteAddr netip.AddrPort) []extDNSEntry {
if res, ok := r.ipToExtDNS.get(remoteAddr.Addr()); ok {
return res[:]
}
return r.extDNSList[:]
}
func (r *Resolver) exchange(ctx context.Context, proto string, extDNS extDNSEntry, query *dns.Msg) *dns.Msg {
ctx, span := otel.Tracer("").Start(ctx, "resolver.exchange", trace.WithAttributes(
attribute.String("libnet.resolver.upstream.proto", proto),

View file

@ -92,6 +92,7 @@ type resolvConfPathConfig struct {
}
type containerConfig struct {
containerConfigOS //nolint:nolintlint,unused // only populated on windows
hostsPathConfig
resolvConfPathConfig
generic map[string]interface{}
@ -507,6 +508,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) {

View file

@ -4,6 +4,7 @@ package libnetwork
import (
"context"
"fmt"
"io/fs"
"net/netip"
"os"
@ -48,7 +49,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
@ -343,9 +344,9 @@ func (sb *Sandbox) rebuildDNS() error {
}
}
intNS, err := netip.ParseAddr(sb.resolver.NameServer())
if err != nil {
return err
intNS := sb.resolver.NameServer()
if !intNS.IsValid() {
return fmt.Errorf("no listen-address for internal resolver")
}
// Work out whether ndots has been set from host config or overrides.

View file

@ -12,6 +12,9 @@ import (
"github.com/docker/docker/libnetwork/types"
)
// Linux-specific container configuration flags.
type containerConfigOS struct{} //nolint:nolintlint,unused // only populated on windows
func releaseOSSboxResources(ns *osl.Namespace, ep *Endpoint) {
for _, i := range ns.Interfaces() {
// Only remove the interfaces owned by this endpoint from the sandbox.
@ -90,12 +93,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 +161,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 +178,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 +307,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))

View file

@ -0,0 +1,7 @@
package libnetwork
func OptionDNSNoProxy() SandboxOption {
return func(sb *Sandbox) {
sb.config.dnsNoProxy = true
}
}

View file

@ -1,9 +1,12 @@
//go:build !linux
package libnetwork
import "github.com/docker/docker/libnetwork/osl"
// Windows-specific container configuration flags.
type containerConfigOS struct {
dnsNoProxy bool
}
func releaseOSSboxResources(*osl.Namespace, *Endpoint) {}
func (sb *Sandbox) updateGateway(*Endpoint) error {

View file

@ -0,0 +1,15 @@
package system // import "github.com/docker/docker/pkg/system"
import "syscall"
// fromStatT converts a syscall.Stat_t type to a system.Stat_t type
func fromStatT(s *syscall.Stat_t) (*StatT, error) {
return &StatT{
size: s.Size,
mode: uint32(s.Mode),
uid: s.Uid,
gid: s.Gid,
rdev: uint64(s.Rdev),
mtim: s.Mtim,
}, nil
}

View file

@ -48,7 +48,7 @@ func (p *profileData) generateDefault(out io.Writer) error {
return compiled.Execute(out, p)
}
// macrosExists checks if the passed macro exists.
// macroExists checks if the passed macro exists.
func macroExists(m string) bool {
_, err := os.Stat(path.Join(profileDirectory, m))
return err == nil

View file

@ -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
@ -66,7 +66,7 @@ require (
github.com/moby/locker v1.0.1
github.com/moby/patternmatcher v0.6.0
github.com/moby/pubsub v1.0.0
github.com/moby/swarmkit/v2 v2.0.0-20240125134710-dcda100a8261
github.com/moby/swarmkit/v2 v2.0.0-20240412154004-f3ffc0881d0e
github.com/moby/sys/mount v0.3.3
github.com/moby/sys/mountinfo v0.7.1
github.com/moby/sys/sequential v0.5.0
@ -78,7 +78,7 @@ require (
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0-rc5
github.com/opencontainers/runc v1.1.12
github.com/opencontainers/runtime-spec v1.1.0
github.com/opencontainers/runtime-spec v1.2.0
github.com/opencontainers/selinux v1.11.0
github.com/pelletier/go-toml v1.9.5
github.com/pkg/errors v0.9.1
@ -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
@ -110,7 +110,7 @@ require (
google.golang.org/protobuf v1.33.0
gotest.tools/v3 v3.5.1
resenje.org/singleflight v0.4.1
tags.cncf.io/container-device-interface v0.6.2
tags.cncf.io/container-device-interface v0.7.1
)
require (
@ -132,6 +132,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.13.0 // indirect
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
@ -147,6 +148,7 @@ require (
github.com/containernetworking/cni v1.1.2 // indirect
github.com/containernetworking/plugins v1.4.0 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
@ -179,6 +181,7 @@ require (
github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626 // indirect
github.com/package-url/packageurl-go v0.1.1-0.20220428063043-89078438f170 // indirect
github.com/philhofer/fwd v1.1.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
@ -186,6 +189,7 @@ require (
github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/spdx/tools-golang v0.5.3 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
github.com/tinylib/msgp v1.1.8 // indirect
github.com/tonistiigi/go-actions-cache v0.0.0-20240227172821-a0b64f338598 // indirect
@ -212,7 +216,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
@ -221,7 +225,8 @@ require (
google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.90.1 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
tags.cncf.io/container-device-interface/specs-go v0.6.0 // indirect
tags.cncf.io/container-device-interface/specs-go v0.7.0 // indirect
)

View file

@ -103,6 +103,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
@ -154,8 +156,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=
@ -488,8 +490,8 @@ github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkV
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/pubsub v1.0.0 h1:jkp/imWsmJz2f6LyFsk7EkVeN2HxR/HTTOY8kHrsxfA=
github.com/moby/pubsub v1.0.0/go.mod h1:bXSO+3h5MNXXCaEG+6/NlAIk7MMZbySZlnB+cUQhKKc=
github.com/moby/swarmkit/v2 v2.0.0-20240125134710-dcda100a8261 h1:mjLf2jYrqtIS4LvLzg0gNyJR4rMXS4X5Bg1A4hOhVMs=
github.com/moby/swarmkit/v2 v2.0.0-20240125134710-dcda100a8261/go.mod h1:oRJU1d0hrkkwCtouwfQGcIAKcVEkclMYoLWocqrg6gI=
github.com/moby/swarmkit/v2 v2.0.0-20240412154004-f3ffc0881d0e h1:ZCx3cYIxF6OdZkiSbesvyJyJetMmICNvMGVHFop+Mec=
github.com/moby/swarmkit/v2 v2.0.0-20240412154004-f3ffc0881d0e/go.mod h1:kNy225f/gWAnF8wPftteMc5nbAHhrH+HUfvyjmhFjeQ=
github.com/moby/sys/mount v0.3.3 h1:fX1SVkXFJ47XWDoeFW4Sq7PdQJnV2QIDZAqjNqgEjUs=
github.com/moby/sys/mount v0.3.3/go.mod h1:PBaEorSNTLG5t/+4EgukEQVlAvVEc6ZjTySwKdqp5K0=
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
@ -540,8 +542,8 @@ github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVn
github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss=
github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8=
github.com/opencontainers/runtime-spec v1.0.3-0.20220825212826-86290f6a00fb/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg=
github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk=
github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626 h1:DmNGcqH3WDbV5k8OJ+esPWbqUOX5rMLR2PMvziDMJi0=
github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626/go.mod h1:BRHJJd0E+cx42OybVYSgUvZmU0B8P9gZuRXlZUP7TKI=
github.com/opencontainers/selinux v1.9.1/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
@ -665,6 +667,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/tedsuo/ifrit v0.0.0-20230330192023-5cba443a66c4 h1:MGZzzxBuPuK4J0XQo+0uy0NnXQGKzHXhYp5oG1Wy860=
github.com/tedsuo/ifrit v0.0.0-20230330192023-5cba443a66c4/go.mod h1:eyZnKCc955uh98WQvzOm0dgAeLnf2O0Rz0LPoC5ze+0=
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@ -796,8 +800,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 +862,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 +942,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=
@ -1102,7 +1106,7 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
tags.cncf.io/container-device-interface v0.6.2 h1:dThE6dtp/93ZDGhqaED2Pu374SOeUkBfuvkLuiTdwzg=
tags.cncf.io/container-device-interface v0.6.2/go.mod h1:Shusyhjs1A5Na/kqPVLL0KqnHQHuunol9LFeUNkuGVE=
tags.cncf.io/container-device-interface/specs-go v0.6.0 h1:V+tJJN6dqu8Vym6p+Ru+K5mJ49WL6Aoc5SJFSY0RLsQ=
tags.cncf.io/container-device-interface/specs-go v0.6.0/go.mod h1:hMAwAbMZyBLdmYqWgYcKH0F/yctNpV3P35f+/088A80=
tags.cncf.io/container-device-interface v0.7.1 h1:MATNCbAD1su9U6zwQe5BrQ2vGGp1GBayD70bYaxYCNE=
tags.cncf.io/container-device-interface v0.7.1/go.mod h1:h1JVuOqTQVORp8DziaWKUCDNzAmN+zeCbqbqD30D0ZQ=
tags.cncf.io/container-device-interface/specs-go v0.7.0 h1:w/maMGVeLP6TIQJVYT5pbqTi8SCw/iHZ+n4ignuGHqg=
tags.cncf.io/container-device-interface/specs-go v0.7.0/go.mod h1:hMAwAbMZyBLdmYqWgYcKH0F/yctNpV3P35f+/088A80=

View file

@ -0,0 +1,139 @@
package fakeclock
import (
"errors"
"sync"
"time"
"code.cloudfoundry.org/clock"
)
type timeWatcher interface {
timeUpdated(time.Time)
shouldFire(time.Time) bool
repeatable() bool
}
type FakeClock struct {
now time.Time
watchers map[timeWatcher]struct{}
cond *sync.Cond
}
func NewFakeClock(now time.Time) *FakeClock {
return &FakeClock{
now: now,
watchers: make(map[timeWatcher]struct{}),
cond: &sync.Cond{L: &sync.Mutex{}},
}
}
func (clock *FakeClock) Since(t time.Time) time.Duration {
return clock.Now().Sub(t)
}
func (clock *FakeClock) Now() time.Time {
clock.cond.L.Lock()
defer clock.cond.L.Unlock()
return clock.now
}
func (clock *FakeClock) Increment(duration time.Duration) {
clock.increment(duration, false, 0)
}
func (clock *FakeClock) IncrementBySeconds(seconds uint64) {
clock.Increment(time.Duration(seconds) * time.Second)
}
func (clock *FakeClock) WaitForWatcherAndIncrement(duration time.Duration) {
clock.WaitForNWatchersAndIncrement(duration, 1)
}
func (clock *FakeClock) WaitForNWatchersAndIncrement(duration time.Duration, numWatchers int) {
clock.increment(duration, true, numWatchers)
}
func (clock *FakeClock) NewTimer(d time.Duration) clock.Timer {
timer := newFakeTimer(clock, d, false)
clock.addTimeWatcher(timer)
return timer
}
func (clock *FakeClock) Sleep(d time.Duration) {
<-clock.NewTimer(d).C()
}
func (clock *FakeClock) After(d time.Duration) <-chan time.Time {
return clock.NewTimer(d).C()
}
func (clock *FakeClock) NewTicker(d time.Duration) clock.Ticker {
if d <= 0 {
panic(errors.New("duration must be greater than zero"))
}
timer := newFakeTimer(clock, d, true)
clock.addTimeWatcher(timer)
return newFakeTicker(timer)
}
func (clock *FakeClock) WatcherCount() int {
clock.cond.L.Lock()
defer clock.cond.L.Unlock()
return len(clock.watchers)
}
func (clock *FakeClock) increment(duration time.Duration, waitForWatchers bool, numWatchers int) {
clock.cond.L.Lock()
for waitForWatchers && len(clock.watchers) < numWatchers {
clock.cond.Wait()
}
now := clock.now.Add(duration)
clock.now = now
watchers := make([]timeWatcher, 0)
newWatchers := map[timeWatcher]struct{}{}
for w, _ := range clock.watchers {
fire := w.shouldFire(now)
if fire {
watchers = append(watchers, w)
}
if !fire || w.repeatable() {
newWatchers[w] = struct{}{}
}
}
clock.watchers = newWatchers
clock.cond.L.Unlock()
for _, w := range watchers {
w.timeUpdated(now)
}
}
func (clock *FakeClock) addTimeWatcher(tw timeWatcher) {
clock.cond.L.Lock()
clock.watchers[tw] = struct{}{}
clock.cond.L.Unlock()
// force the timer to fire
clock.Increment(0)
clock.cond.Broadcast()
}
func (clock *FakeClock) removeTimeWatcher(tw timeWatcher) {
clock.cond.L.Lock()
delete(clock.watchers, tw)
clock.cond.L.Unlock()
}

View file

@ -0,0 +1,25 @@
package fakeclock
import (
"time"
"code.cloudfoundry.org/clock"
)
type fakeTicker struct {
timer clock.Timer
}
func newFakeTicker(timer *fakeTimer) *fakeTicker {
return &fakeTicker{
timer: timer,
}
}
func (ft *fakeTicker) C() <-chan time.Time {
return ft.timer.C()
}
func (ft *fakeTicker) Stop() {
ft.timer.Stop()
}

View file

@ -0,0 +1,87 @@
package fakeclock
import (
"sync"
"time"
)
type fakeTimer struct {
clock *FakeClock
mutex sync.Mutex
completionTime time.Time
channel chan time.Time
duration time.Duration
repeat bool
}
func newFakeTimer(clock *FakeClock, d time.Duration, repeat bool) *fakeTimer {
return &fakeTimer{
clock: clock,
completionTime: clock.Now().Add(d),
channel: make(chan time.Time, 1),
duration: d,
repeat: repeat,
}
}
func (ft *fakeTimer) C() <-chan time.Time {
ft.mutex.Lock()
defer ft.mutex.Unlock()
return ft.channel
}
func (ft *fakeTimer) reset(d time.Duration) bool {
currentTime := ft.clock.Now()
ft.mutex.Lock()
active := !ft.completionTime.IsZero()
ft.completionTime = currentTime.Add(d)
ft.mutex.Unlock()
return active
}
func (ft *fakeTimer) Reset(d time.Duration) bool {
active := ft.reset(d)
ft.clock.addTimeWatcher(ft)
return active
}
func (ft *fakeTimer) Stop() bool {
ft.mutex.Lock()
active := !ft.completionTime.IsZero()
ft.mutex.Unlock()
ft.clock.removeTimeWatcher(ft)
return active
}
func (ft *fakeTimer) shouldFire(now time.Time) bool {
ft.mutex.Lock()
defer ft.mutex.Unlock()
if ft.completionTime.IsZero() {
return false
}
return now.After(ft.completionTime) || now.Equal(ft.completionTime)
}
func (ft *fakeTimer) repeatable() bool {
return ft.repeat
}
func (ft *fakeTimer) timeUpdated(now time.Time) {
select {
case ft.channel <- now:
default:
// drop on the floor. timers have a buffered channel anyway. according to
// godoc of the `time' package a ticker can loose ticks in case of a slow
// receiver
}
if ft.repeatable() {
ft.reset(ft.duration)
}
}

View file

@ -0,0 +1 @@
package fakeclock // import "code.cloudfoundry.org/clock/fakeclock"

26
vendor/github.com/bits-and-blooms/bitset/.gitignore generated vendored Normal file
View file

@ -0,0 +1,26 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
target

37
vendor/github.com/bits-and-blooms/bitset/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,37 @@
language: go
sudo: false
branches:
except:
- release
branches:
only:
- master
- travis
go:
- "1.11.x"
- tip
matrix:
allow_failures:
- go: tip
before_install:
- if [ -n "$GH_USER" ]; then git config --global github.user ${GH_USER}; fi;
- if [ -n "$GH_TOKEN" ]; then git config --global github.token ${GH_TOKEN}; fi;
- go get github.com/mattn/goveralls
before_script:
- make deps
script:
- make qa
after_failure:
- cat ./target/test/report.xml
after_success:
- if [ "$TRAVIS_GO_VERSION" = "1.11.1" ]; then $HOME/gopath/bin/goveralls -covermode=count -coverprofile=target/report/coverage.out -service=travis-ci; fi;

27
vendor/github.com/bits-and-blooms/bitset/LICENSE generated vendored Normal file
View file

@ -0,0 +1,27 @@
Copyright (c) 2014 Will Fitzgerald. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

159
vendor/github.com/bits-and-blooms/bitset/README.md generated vendored Normal file
View file

@ -0,0 +1,159 @@
# bitset
*Go language library to map between non-negative integers and boolean values*
[![Test](https://github.com/bits-and-blooms/bitset/workflows/Test/badge.svg)](https://github.com/willf/bitset/actions?query=workflow%3ATest)
[![Go Report Card](https://goreportcard.com/badge/github.com/willf/bitset)](https://goreportcard.com/report/github.com/willf/bitset)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/bits-and-blooms/bitset?tab=doc)](https://pkg.go.dev/github.com/bits-and-blooms/bitset?tab=doc)
This library is part of the [awesome go collection](https://github.com/avelino/awesome-go). It is used in production by several important systems:
* [beego](https://github.com/beego/beego)
* [CubeFS](https://github.com/cubefs/cubefs)
* [Amazon EKS Distro](https://github.com/aws/eks-distro)
* [sourcegraph](https://github.com/sourcegraph/sourcegraph)
* [torrent](https://github.com/anacrolix/torrent)
## Description
Package bitset implements bitsets, a mapping between non-negative integers and boolean values.
It should be more efficient than map[uint] bool.
It provides methods for setting, clearing, flipping, and testing individual integers.
But it also provides set intersection, union, difference, complement, and symmetric operations, as well as tests to check whether any, all, or no bits are set, and querying a bitset's current length and number of positive bits.
BitSets are expanded to the size of the largest set bit; the memory allocation is approximately Max bits, where Max is the largest set bit. BitSets are never shrunk. On creation, a hint can be given for the number of bits that will be used.
Many of the methods, including Set, Clear, and Flip, return a BitSet pointer, which allows for chaining.
### Example use:
```go
package main
import (
"fmt"
"math/rand"
"github.com/bits-and-blooms/bitset"
)
func main() {
fmt.Printf("Hello from BitSet!\n")
var b bitset.BitSet
// play some Go Fish
for i := 0; i < 100; i++ {
card1 := uint(rand.Intn(52))
card2 := uint(rand.Intn(52))
b.Set(card1)
if b.Test(card2) {
fmt.Println("Go Fish!")
}
b.Clear(card1)
}
// Chaining
b.Set(10).Set(11)
for i, e := b.NextSet(0); e; i, e = b.NextSet(i + 1) {
fmt.Println("The following bit is set:", i)
}
if b.Intersection(bitset.New(100).Set(10)).Count() == 1 {
fmt.Println("Intersection works.")
} else {
fmt.Println("Intersection doesn't work???")
}
}
```
Package documentation is at: https://pkg.go.dev/github.com/bits-and-blooms/bitset?tab=doc
## Serialization
You may serialize a bitset safely and portably to a stream
of bytes as follows:
```Go
const length = 9585
const oneEvery = 97
bs := bitset.New(length)
// Add some bits
for i := uint(0); i < length; i += oneEvery {
bs = bs.Set(i)
}
var buf bytes.Buffer
n, err := bs.WriteTo(&buf)
if err != nil {
// failure
}
// Here n == buf.Len()
```
You can later deserialize the result as follows:
```Go
// Read back from buf
bs = bitset.New()
n, err = bs.ReadFrom(&buf)
if err != nil {
// error
}
// n is the number of bytes read
```
The `ReadFrom` function attempts to read the data into the existing
BitSet instance, to minimize memory allocations.
*Performance tip*:
When reading and writing to a file or a network connection, you may get better performance by
wrapping your streams with `bufio` instances.
E.g.,
```Go
f, err := os.Create("myfile")
w := bufio.NewWriter(f)
```
```Go
f, err := os.Open("myfile")
r := bufio.NewReader(f)
```
## Memory Usage
The memory usage of a bitset using `N` bits is at least `N/8` bytes. The number of bits in a bitset is at least as large as one plus the greatest bit index you have accessed. Thus it is possible to run out of memory while using a bitset. If you have lots of bits, you might prefer compressed bitsets, like the [Roaring bitmaps](http://roaringbitmap.org) and its [Go implementation](https://github.com/RoaringBitmap/roaring).
The `roaring` library allows you to go back and forth between compressed Roaring bitmaps and the conventional bitset instances:
```Go
mybitset := roaringbitmap.ToBitSet()
newroaringbitmap := roaring.FromBitSet(mybitset)
```
## Implementation Note
Go 1.9 introduced a native `math/bits` library. We provide backward compatibility to Go 1.7, which might be removed.
It is possible that a later version will match the `math/bits` return signature for counts (which is `int`, rather than our library's `uint64`). If so, the version will be bumped.
## Installation
```bash
go get github.com/bits-and-blooms/bitset
```
## Contributing
If you wish to contribute to this project, please branch and issue a pull request against master ("[GitHub Flow](https://guides.github.com/introduction/flow/)")
## Running all tests
Before committing the code, please check if it passes tests, has adequate coverage, etc.
```bash
go test
go test -cover
```

5
vendor/github.com/bits-and-blooms/bitset/SECURITY.md generated vendored Normal file
View file

@ -0,0 +1,5 @@
# Security Policy
## Reporting a Vulnerability
You can report privately a vulnerability by email at daniel@lemire.me (current maintainer).

View file

@ -0,0 +1,39 @@
# Go
# Build your Go project.
# Add steps that test, save build artifacts, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/go
trigger:
- master
pool:
vmImage: 'Ubuntu-16.04'
variables:
GOBIN: '$(GOPATH)/bin' # Go binaries path
GOROOT: '/usr/local/go1.11' # Go installation path
GOPATH: '$(system.defaultWorkingDirectory)/gopath' # Go workspace path
modulePath: '$(GOPATH)/src/github.com/$(build.repository.name)' # Path to the module's code
steps:
- script: |
mkdir -p '$(GOBIN)'
mkdir -p '$(GOPATH)/pkg'
mkdir -p '$(modulePath)'
shopt -s extglob
shopt -s dotglob
mv !(gopath) '$(modulePath)'
echo '##vso[task.prependpath]$(GOBIN)'
echo '##vso[task.prependpath]$(GOROOT)/bin'
displayName: 'Set up the Go workspace'
- script: |
go version
go get -v -t -d ./...
if [ -f Gopkg.toml ]; then
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep ensure
fi
go build -v .
workingDirectory: '$(modulePath)'
displayName: 'Get dependencies, then build'

1184
vendor/github.com/bits-and-blooms/bitset/bitset.go generated vendored Normal file

File diff suppressed because it is too large Load diff

53
vendor/github.com/bits-and-blooms/bitset/popcnt.go generated vendored Normal file
View file

@ -0,0 +1,53 @@
package bitset
// bit population count, take from
// https://code.google.com/p/go/issues/detail?id=4988#c11
// credit: https://code.google.com/u/arnehormann/
func popcount(x uint64) (n uint64) {
x -= (x >> 1) & 0x5555555555555555
x = (x>>2)&0x3333333333333333 + x&0x3333333333333333
x += x >> 4
x &= 0x0f0f0f0f0f0f0f0f
x *= 0x0101010101010101
return x >> 56
}
func popcntSliceGo(s []uint64) uint64 {
cnt := uint64(0)
for _, x := range s {
cnt += popcount(x)
}
return cnt
}
func popcntMaskSliceGo(s, m []uint64) uint64 {
cnt := uint64(0)
for i := range s {
cnt += popcount(s[i] &^ m[i])
}
return cnt
}
func popcntAndSliceGo(s, m []uint64) uint64 {
cnt := uint64(0)
for i := range s {
cnt += popcount(s[i] & m[i])
}
return cnt
}
func popcntOrSliceGo(s, m []uint64) uint64 {
cnt := uint64(0)
for i := range s {
cnt += popcount(s[i] | m[i])
}
return cnt
}
func popcntXorSliceGo(s, m []uint64) uint64 {
cnt := uint64(0)
for i := range s {
cnt += popcount(s[i] ^ m[i])
}
return cnt
}

62
vendor/github.com/bits-and-blooms/bitset/popcnt_19.go generated vendored Normal file
View file

@ -0,0 +1,62 @@
//go:build go1.9
// +build go1.9
package bitset
import "math/bits"
func popcntSlice(s []uint64) uint64 {
var cnt int
for _, x := range s {
cnt += bits.OnesCount64(x)
}
return uint64(cnt)
}
func popcntMaskSlice(s, m []uint64) uint64 {
var cnt int
// this explicit check eliminates a bounds check in the loop
if len(m) < len(s) {
panic("mask slice is too short")
}
for i := range s {
cnt += bits.OnesCount64(s[i] &^ m[i])
}
return uint64(cnt)
}
func popcntAndSlice(s, m []uint64) uint64 {
var cnt int
// this explicit check eliminates a bounds check in the loop
if len(m) < len(s) {
panic("mask slice is too short")
}
for i := range s {
cnt += bits.OnesCount64(s[i] & m[i])
}
return uint64(cnt)
}
func popcntOrSlice(s, m []uint64) uint64 {
var cnt int
// this explicit check eliminates a bounds check in the loop
if len(m) < len(s) {
panic("mask slice is too short")
}
for i := range s {
cnt += bits.OnesCount64(s[i] | m[i])
}
return uint64(cnt)
}
func popcntXorSlice(s, m []uint64) uint64 {
var cnt int
// this explicit check eliminates a bounds check in the loop
if len(m) < len(s) {
panic("mask slice is too short")
}
for i := range s {
cnt += bits.OnesCount64(s[i] ^ m[i])
}
return uint64(cnt)
}

View file

@ -0,0 +1,68 @@
//go:build !go1.9 && amd64 && !appengine
// +build !go1.9,amd64,!appengine
package bitset
// *** the following functions are defined in popcnt_amd64.s
//go:noescape
func hasAsm() bool
// useAsm is a flag used to select the GO or ASM implementation of the popcnt function
var useAsm = hasAsm()
//go:noescape
func popcntSliceAsm(s []uint64) uint64
//go:noescape
func popcntMaskSliceAsm(s, m []uint64) uint64
//go:noescape
func popcntAndSliceAsm(s, m []uint64) uint64
//go:noescape
func popcntOrSliceAsm(s, m []uint64) uint64
//go:noescape
func popcntXorSliceAsm(s, m []uint64) uint64
func popcntSlice(s []uint64) uint64 {
if useAsm {
return popcntSliceAsm(s)
}
return popcntSliceGo(s)
}
func popcntMaskSlice(s, m []uint64) uint64 {
if useAsm {
return popcntMaskSliceAsm(s, m)
}
return popcntMaskSliceGo(s, m)
}
func popcntAndSlice(s, m []uint64) uint64 {
if useAsm {
return popcntAndSliceAsm(s, m)
}
return popcntAndSliceGo(s, m)
}
func popcntOrSlice(s, m []uint64) uint64 {
if useAsm {
return popcntOrSliceAsm(s, m)
}
return popcntOrSliceGo(s, m)
}
func popcntXorSlice(s, m []uint64) uint64 {
if useAsm {
return popcntXorSliceAsm(s, m)
}
return popcntXorSliceGo(s, m)
}

Some files were not shown because too many files have changed in this diff Show more