From c21e3b96f34361677dfbe08aec61b6a622b9642d Mon Sep 17 00:00:00 2001 From: Dmitriy Matrenichev Date: Tue, 29 Oct 2024 22:00:44 +0300 Subject: [PATCH] feat: support conditional start of IPv6 dns servers This PR does those things: - [x] Refactored `DNSResolveCacheController`. Most of the logic moved to `dns` package types. Simplify and streamline logic. - [x] Replace most of the goroutine orchestration with suture package. - [x] Support per-item reaction to the dns listeners/servers failing to start. This allows us to ignore IPv6 errors if it's disabled. - [x] Support per-item reaction to the dns listeners/servers failing to stop. - [ ] Raise IPv6 listener on link-local address for dns (both TCP and UDP). - [ ] Update kubelet's `resolv.conf` IPv4/IPv6 endpoints. Closes #9384 Signed-off-by: Dmitriy Matrenichev --- go.mod | 10 +- go.sum | 92 +---- .../controllers/network/dns_resolve_cache.go | 346 ++++++------------ .../network/dns_resolve_cache_test.go | 1 + .../pkg/controllers/network/etcfile.go | 11 +- .../pkg/controllers/network/etcfile_test.go | 12 +- .../pkg/controllers/network/hostdns_config.go | 46 ++- .../network/nftables_chain_config.go | 22 +- .../app/machined/pkg/xcontext/xcontext.go | 28 ++ internal/pkg/dns/dns.go | 102 +----- internal/pkg/dns/dns_test.go | 169 ++++++--- internal/pkg/dns/manager.go | 318 ++++++++++++++++ internal/pkg/dns/runnner.go | 108 ++++++ pkg/machinery/constants/constants.go | 5 + .../resources/network/hostdns_config.go | 9 +- 15 files changed, 778 insertions(+), 501 deletions(-) create mode 100644 internal/app/machined/pkg/xcontext/xcontext.go create mode 100644 internal/pkg/dns/manager.go create mode 100644 internal/pkg/dns/runnner.go diff --git a/go.mod b/go.mod index 401901f1136..d062f7eb49d 100644 --- a/go.mod +++ b/go.mod @@ -171,6 +171,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.9.0 + github.com/thejerf/suture/v4 v4.0.5 github.com/u-root/u-root v0.14.0 github.com/ulikunitz/xz v0.5.12 github.com/vmware/vmw-guestinfo v0.0.0-20220317130741-510905f0efa3 @@ -230,10 +231,8 @@ require ( github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/checkpoint-restore/go-criu/v6 v6.3.0 // indirect github.com/cilium/ebpf v0.16.0 // indirect github.com/cloudflare/circl v1.3.9 // indirect - github.com/containerd/console v1.0.4 // indirect github.com/containerd/containerd v1.7.23 // indirect github.com/containerd/continuity v0.4.3 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect @@ -247,7 +246,6 @@ require ( github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/cyphar/filepath-securejoin v0.3.4 // indirect - github.com/dave/jennifer v1.6.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect @@ -287,8 +285,6 @@ require ( github.com/hashicorp/go-version v1.6.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jessevdk/go-flags v1.4.1-0.20181029123624-5de817a9aa20 // indirect - github.com/jmattheis/goverter v1.5.1 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/josharian/native v1.1.0 // indirect @@ -319,7 +315,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect - github.com/mrunalp/fileutils v0.5.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d // indirect @@ -337,16 +332,13 @@ require ( github.com/prometheus/common v0.55.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/seccomp/libseccomp-golang v0.10.0 // indirect github.com/siderolabs/tcpproxy v0.1.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/afero v1.10.0 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect - github.com/urfave/cli v1.22.15 // indirect github.com/vbatts/tar-split v0.11.3 // indirect - github.com/vburenin/ifacemaker v1.2.1 // indirect github.com/vishvananda/netlink v1.3.0 // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/x448/float16 v0.8.4 // indirect diff --git a/go.sum b/go.sum index 5d0000e5745..1b04edf5897 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,6 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 h1:59MxjQVfjXsBpLy+dbd2/ELV5ofnUkUZBvWSC85sheA= github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.15.0 h1:eXzkOEXbSTOa7cJ7EqeCVi/OFi/ppDrUtQuttCWy74c= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.15.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g= @@ -22,16 +20,10 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvUL github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.1.0 h1:iqsGTcqW10igLT4gfeQGWTiZzH5U5z3SjdGrylJ3Riw= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.1.0/go.mod h1:AbVj1nFPV+Gd+rRX91BQ6F4/g5IaP24k8An4gJusZXs= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.2.0 h1:bOWx0pDRwHudx53ElibbtlzzcJbIC9foYGdqHQgi+88= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.2.0/go.mod h1:r+2gKvCfqxaDW1a9mtIH9vJ0lT0ULJrKBGwbCPPjEwg= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0 h1:DRiANoJTiW6obBQe3SqZizkuV1PEgfiiGivmVocDy64= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0/go.mod h1:qLIye2hwb/ZouqhpSD9Zn3SJipvpEnz1Ywl3VUk9Y0s= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.2.0 h1:fKSH2aGSnt/ibGvis2f0gD3Lp10bzKr92FQxKutoNDc= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.2.0/go.mod h1:TPR8de/4RyFL97NXnpjtIaLZFR0eQxlQeXbk7EKIrbw= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 h1:eXnN9kaS8TiDwXjoie3hMRLuwdUBUMW9KRgOqB3mCaw= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0/go.mod h1:XIpam8wumeZ5rVMuhdDQLMfIPDf1WO3IzrCRO3e3e3o= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= @@ -42,7 +34,6 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mx github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= @@ -71,52 +62,30 @@ github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hC github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/aws/aws-sdk-go-v2 v1.32.2 h1:AkNLZEyYMLnx/Q/mSKkcMqwNFXMAvFto9bNsHqcTduI= -github.com/aws/aws-sdk-go-v2 v1.32.2/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= github.com/aws/aws-sdk-go-v2 v1.32.3 h1:T0dRlFBKcdaUPGNtkBSwHZxrtis8CQU17UpNBZYd0wk= github.com/aws/aws-sdk-go-v2 v1.32.3/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= -github.com/aws/aws-sdk-go-v2/config v1.28.0 h1:FosVYWcqEtWNxHn8gB/Vs6jOlNwSoyOCA/g/sxyySOQ= -github.com/aws/aws-sdk-go-v2/config v1.28.0/go.mod h1:pYhbtvg1siOOg8h5an77rXle9tVG8T+BWLWAo7cOukc= github.com/aws/aws-sdk-go-v2/config v1.28.1 h1:oxIvOUXy8x0U3fR//0eq+RdCKimWI900+SV+10xsCBw= github.com/aws/aws-sdk-go-v2/config v1.28.1/go.mod h1:bRQcttQJiARbd5JZxw6wG0yIK3eLeSCPdg6uqmmlIiI= -github.com/aws/aws-sdk-go-v2/credentials v1.17.41 h1:7gXo+Axmp+R4Z+AK8YFQO0ZV3L0gizGINCOWxSLY9W8= -github.com/aws/aws-sdk-go-v2/credentials v1.17.41/go.mod h1:u4Eb8d3394YLubphT4jLEwN1rLNq2wFOlT6OuxFwPzU= github.com/aws/aws-sdk-go-v2/credentials v1.17.42 h1:sBP0RPjBU4neGpIYyx8mkU2QqLPl5u9cmdTWVzIpHkM= github.com/aws/aws-sdk-go-v2/credentials v1.17.42/go.mod h1:FwZBfU530dJ26rv9saAbxa9Ej3eF/AK0OAY86k13n4M= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 h1:TMH3f/SCAWdNtXXVPPu5D6wrr4G5hI1rAxbcocKfC7Q= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17/go.mod h1:1ZRXLdTpzdJb9fwTMXiLipENRxkGMTn1sfKexGllQCw= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.18 h1:68jFVtt3NulEzojFesM/WVarlFpCaXLKaBxDpzkQ9OQ= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.18/go.mod h1:Fjnn5jQVIo6VyedMc0/EhPpfNlPl7dHV916O6B+49aE= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 h1:UAsR3xA31QGf79WzpG/ixT9FZvQlh5HY1NRqSHBNOCk= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21/go.mod h1:JNr43NFf5L9YaG3eKTm7HQzls9J+A9YYcGI5Quh1r2Y= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 h1:Jw50LwEkVjuVzE1NzkhNKkBf9cRN7MtE1F/b2cOKTUM= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22/go.mod h1:Y/SmAyPcOTmpeVaWSzSKiILfXTVJwrGmYZhcRbhWuEY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 h1:6jZVETqmYCadGFvrYEQfC5fAQmlo80CeL5psbno6r0s= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21/go.mod h1:1SR0GbLlnN3QUmYaflZNiH1ql+1qrSiB2vwcJ+4UM60= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 h1:981MHwBaRZM7+9QSR6XamDzF/o7ouUGxFzr+nVSIhrs= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22/go.mod h1:1RA1+aBEfn+CAB/Mh0MB6LsdCYCnjZm7tKXtnk499ZQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 h1:s7NA1SOw8q/5c0wr8477yOPp0z+uBaXBnLE0XYb0POA= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2/go.mod h1:fnjjWyAW/Pj5HYOxl9LJqWtEwS7W2qgcRLWP+uWbss0= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3 h1:qcxX0JYlgWH3hpPUnd6U0ikcl6LLA9sLkXE2w1fpMvY= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3/go.mod h1:cLSNEmI45soc+Ef8K/L+8sEA3A3pYFEYf5B5UI+6bH4= -github.com/aws/aws-sdk-go-v2/service/kms v1.37.2 h1:tfBABi5R6aSZlhgTWHxL+opYUDOnIGoNcJLwVYv0jLM= -github.com/aws/aws-sdk-go-v2/service/kms v1.37.2/go.mod h1:dZYFcQwuoh+cLOlFnZItijZptmyDhRIkOKWFO1CfzV8= github.com/aws/aws-sdk-go-v2/service/kms v1.37.3 h1:VpyBA6KP6JgzwokQps8ArQPGy9rFej8adwuuQGcduH8= github.com/aws/aws-sdk-go-v2/service/kms v1.37.3/go.mod h1:TT/9V4PcmSPpd8LPUNJ8hBHJmpqcfhx6MrbWTkvyR+4= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 h1:bSYXVyUzoTHoKalBmwaZxs97HU9DWWI3ehHSAMa7xOk= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.2/go.mod h1:skMqY7JElusiOUjMJMOv1jJsP7YUg7DrhgqZZWuzu1U= github.com/aws/aws-sdk-go-v2/service/sso v1.24.3 h1:UTpsIf0loCIWEbrqdLb+0RxnTXfWh2vhw4nQmFi4nPc= github.com/aws/aws-sdk-go-v2/service/sso v1.24.3/go.mod h1:FZ9j3PFHHAR+w0BSEjK955w5YD2UwB/l/H0yAK3MJvI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 h1:AhmO1fHINP9vFYUE0LHzCWg/LfUWUF+zFPEcY9QXb7o= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2/go.mod h1:o8aQygT2+MVP0NaV6kbdE1YnnIM8RRVQzoeUH45GOdI= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.3 h1:2YCmIXv3tmiItw0LlYf6v7gEHebLY45kBEnPezbUKyU= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.3/go.mod h1:u19stRyNPxGhj6dRm+Cdgu6N75qnbW7+QN0q0dsAk58= -github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 h1:CiS7i0+FUe+/YY1GvIBLLrR/XNGZ4CtM1Ll0XavNuVo= -github.com/aws/aws-sdk-go-v2/service/sts v1.32.2/go.mod h1:HtaiBI8CjYoNVde8arShXb94UbQQi9L4EMr6D+xGBwo= github.com/aws/aws-sdk-go-v2/service/sts v1.32.3 h1:wVnQ6tigGsRqSWDEEyH6lSAJ9OyFUsSnbaUWChuSGzs= github.com/aws/aws-sdk-go-v2/service/sts v1.32.3/go.mod h1:VZa9yTFyj4o10YGsmDO4gbQJUvvhY72fhumT8W4LqsE= github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= @@ -142,8 +111,6 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= -github.com/checkpoint-restore/go-criu/v6 v6.3.0 h1:mIdrSO2cPNWQY1truPg6uHLXyKHk3Z5Odx4wjKOASzA= -github.com/checkpoint-restore/go-criu/v6 v6.3.0/go.mod h1:rrRTN/uSwY2X+BPRl/gkulo9gsKOSAeVp9/K2tv7xZI= github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/cilium/ebpf v0.8.1/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk= @@ -161,12 +128,8 @@ github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD9 github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= 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.23 h1:H2CClyUkmpKAGlhQp95g2WXHfLYc7whAuvZGBNYOOwQ= github.com/containerd/containerd v1.7.23/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw= -github.com/containerd/containerd/api v1.8.0-rc.4 h1:Z650GHP0OxsoTwwii5U2hyTt7eCRQvvDnRM7pEH/DE0= -github.com/containerd/containerd/api v1.8.0-rc.4/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc= github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0= github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc= github.com/containerd/containerd/v2 v2.0.0-rc.6 h1:uIUmoiXH770KCkTzzqksSpQg7JZ4tC0eM2qXm5EAuwI= @@ -216,8 +179,6 @@ github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.3.4 h1:VBWugsJh2ZxJmLFSM06/0qzQyiQX2Qs0ViKrUAcqdZ8= github.com/cyphar/filepath-securejoin v0.3.4/go.mod h1:8s/MCNJREmFK0H02MF6Ihv1nakJe4L/w3WZLHNkvlYM= -github.com/dave/jennifer v1.6.0 h1:MQ/6emI2xM7wt0tJzJzyUik2Q3Tcn2eE0vtYgh4GPVI= -github.com/dave/jennifer v1.6.0/go.mod h1:AxTG893FiZKqxy3FP1kL80VMshSMuz2G+EgvszgGRnk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -228,8 +189,6 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE= -github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v27.3.1+incompatible h1:qEGdFBF3Xu6SCvCYhc7CzaQTlBmqDuzxPDpigSyeKQQ= github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= @@ -274,16 +233,12 @@ github.com/florianl/go-tc v0.4.4 h1:q6lhEWEfyhGffRzdl3eIcNqX/yVIw0IJwXqa9Rdcctw= github.com/florianl/go-tc v0.4.4/go.mod h1:uvp6pIlOw7Z8hhfnT5M4+V1hHVgZWRZwwMS8Z0JsRxc= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/foxboron/go-uefi v0.0.0-20240805124652-e2076f0e58ca h1:ErxkaWK5AIt8gQf3KpAuQQBdZI4ps72HzEe123kh+So= -github.com/foxboron/go-uefi v0.0.0-20240805124652-e2076f0e58ca/go.mod h1:ffg/fkDeOYicEQLoO2yFFGt00KUTYVXI+rfnc8il6vQ= github.com/foxboron/go-uefi v0.0.0-20241017190036-fab4fdf2f2f3 h1:K8ADp66ulnZ0NhjzwVwE4E3g6Id5KMWu86l0vURusA8= github.com/foxboron/go-uefi v0.0.0-20241017190036-fab4fdf2f2f3/go.mod h1:ffg/fkDeOYicEQLoO2yFFGt00KUTYVXI+rfnc8il6vQ= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= github.com/freddierice/go-losetup/v2 v2.0.1 h1:wPDx/Elu9nDV8y/CvIbEDz5Xi5Zo80y4h7MKbi3XaAI= github.com/freddierice/go-losetup/v2 v2.0.1/go.mod h1:TEyBrvlOelsPEhfWD5rutNXDmUszBXuFnwT1kIQF4J8= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= @@ -313,8 +268,6 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= -github.com/go-resty/resty/v2 v2.9.1 h1:PIgGx4VrHvag0juCJ4dDv3MiFRlDmP0vicBucwf+gLM= -github.com/go-resty/resty/v2 v2.9.1/go.mod h1:4/GYJVjh9nhkhGR6AUNW3XhpDYNUr+Uvy9gV/VGZIy4= github.com/go-resty/resty/v2 v2.15.3 h1:bqff+hcqAflpiF591hhJzNdkRsFhlB96CYfBwSFvql8= github.com/go-resty/resty/v2 v2.15.3/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= @@ -356,8 +309,6 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cadvisor v0.50.0 h1:7w/hKIbJKBWqQsRTy+Hpj2vj+fnxrLXcEXFy+LW0Bsg= -github.com/google/cadvisor v0.50.0/go.mod h1:VxCDwZalpFyENvmfabFqaIGsqNKLtDzE62a19rfVTB8= github.com/google/cadvisor v0.51.0 h1:BspqSPdZoLKrnvuZNOvM/KiJ/A+RdixwagN20n+2H8k= github.com/google/cadvisor v0.51.0/go.mod h1:czGE/c/P/i0QFpVNKTFrIEzord9Y10YfpwuaSWXELc0= github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= @@ -433,8 +384,6 @@ github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mO github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hetznercloud/hcloud-go/v2 v2.13.1 h1:jq0GP4QaYE5d8xR/Zw17s9qoaESRJMXfGmtD1a/qckQ= -github.com/hetznercloud/hcloud-go/v2 v2.13.1/go.mod h1:dhix40Br3fDiBhwaSG/zgaYOFFddpfBm/6R1Zz0IiF0= github.com/hetznercloud/hcloud-go/v2 v2.15.0 h1:6mpMJ/RuX1woZj+MCJdyKNEX9129KDkEIDeeyfr4GD4= github.com/hetznercloud/hcloud-go/v2 v2.15.0/go.mod h1:h8sHav+27Xa+48cVMAvAUMELov5h298Ilg2vflyTHgg= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= @@ -443,15 +392,12 @@ github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8 github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= github.com/hugelgupf/vmtest v0.0.0-20240216064925-0561770280a1 h1:jWoR2Yqg8tzM0v6LAiP7i1bikZJu3gxpgvu3g1Lw+a0= github.com/hugelgupf/vmtest v0.0.0-20240216064925-0561770280a1/go.mod h1:B63hDJMhTupLWCHwopAyEo7wRFowx9kOc8m8j1sfOqE= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= +github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jeromer/syslogparser v1.1.0 h1:HES0EviO9iPvCu56LjVFVhbM3o0BckDlIbQfkkaRJAw= github.com/jeromer/syslogparser v1.1.0/go.mod h1:zfowyus/j2SEgW31bIntTvEBE2zCSndtFsCC6NcW4S4= -github.com/jessevdk/go-flags v1.4.1-0.20181029123624-5de817a9aa20 h1:dAOsPLhnBzIyxu0VvmnKjlNcIlgMK+erD6VRHDtweMI= -github.com/jessevdk/go-flags v1.4.1-0.20181029123624-5de817a9aa20/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmattheis/goverter v1.5.1 h1:NdBYrF1V1EFQbAA1M/ZR4YVbQjxVl3L6Xupn7moF3LU= -github.com/jmattheis/goverter v1.5.1/go.mod h1:iVIl/4qItWjWj2g3vjouGoYensJbRqDHpzlEVMHHFeY= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -501,8 +447,6 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/linode/go-metadata v0.2.0 h1:hlWzkYLa80ikA0NmFX2hcwhcnWFol8F3UIvJnOgdKw4= -github.com/linode/go-metadata v0.2.0/go.mod h1:XraDbSwms0+CtA7/Qh7agkSvGDc6H0s782kpX9MdMu0= github.com/linode/go-metadata v0.2.1 h1:fioxMUqvN6RGLthKINr/XAKsKvg6qdff1dTwoucfpJc= github.com/linode/go-metadata v0.2.1/go.mod h1:6DcmVDcRTMWa+jYAP7R82GWfHE3cNP7tNbWoRwcD3lE= github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= @@ -601,8 +545,6 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= -github.com/mrunalp/fileutils v0.5.1 h1:F+S7ZlNKnrwHfSwdlgNSkKo67ReVf8o9fel6C3dkm/Q= -github.com/mrunalp/fileutils v0.5.1/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= @@ -619,8 +561,6 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/opencontainers/runc v1.2.0 h1:qke7ZVCmJcKrJVY2iHJVC+0kql9uYdkusOPsQOOeBw4= -github.com/opencontainers/runc v1.2.0/go.mod h1:/PXzF0h531HTMsYQnmxXkBD7YaGShm/2zcRB79dksUc= github.com/opencontainers/runc v1.2.1 h1:mQkmeFSUxqFaVmvIn1VQPeQIKpHFya5R07aJw0DKQa8= github.com/opencontainers/runc v1.2.1/go.mod h1:/PXzF0h531HTMsYQnmxXkBD7YaGShm/2zcRB79dksUc= github.com/opencontainers/runtime-spec v1.0.3-0.20220825212826-86290f6a00fb/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= @@ -668,8 +608,6 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= -github.com/rivo/tview v0.0.0-20241016194538-c5e4fb24af13 h1:SG5LUOAzLU9svb9HTLJI2WnLHQDEe86fXWJ4h2fQg0s= -github.com/rivo/tview v0.0.0-20241016194538-c5e4fb24af13/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss= github.com/rivo/tview v0.0.0-20241103174730-c76f7879f592 h1:YIJ+B1hePP6AgynC5TcqpO0H9k3SSoZa2BGyL6vDUzM= github.com/rivo/tview v0.0.0-20241103174730-c76f7879f592/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -692,16 +630,12 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6Ng github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 h1:yoKAVkEVwAqbGbR8n87rHQ1dulL25rKloGadb3vm770= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30/go.mod h1:sH0u6fq6x4R5M7WxkoQFY/o7UaiItec0o1LinLCJNq8= -github.com/seccomp/libseccomp-golang v0.10.0 h1:aA4bp+/Zzi0BnWZ2F1wgNBs5gTpm+na2rWM6M9YjLpY= -github.com/seccomp/libseccomp-golang v0.10.0/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/siderolabs/coredns v1.11.53 h1:HoRPGey3HNj409+15OGnP9Jt4NNpRKsm7izjc/M/G20= github.com/siderolabs/coredns v1.11.53/go.mod h1:2bxje5r6+o9rO0k7bEb5BitqPz8YUYaIY8iJHD1ELtE= github.com/siderolabs/crypto v0.5.0 h1:+Sox0aYLCcD0PAH2cbEcx557zUrONLtuj1Ws+2MFXGc= github.com/siderolabs/crypto v0.5.0/go.mod h1:hsR3tJ3aaeuhCChsLF4dBd9vlJVPvmhg4vvx2ez4aD4= -github.com/siderolabs/discovery-api v0.1.4 h1:2fMEFSMiWaD1zDiBDY5md8VxItvL1rDQRSOfeXNjYKc= -github.com/siderolabs/discovery-api v0.1.4/go.mod h1:kaBy+G42v2xd/uAF/NIe383sjNTBE2AhxPTyi9SZI0s= github.com/siderolabs/discovery-api v0.1.5 h1:fcHVkLkWla7C5+9IeOGEUQ4N8Yp9R7a/kcKbiay2QKw= github.com/siderolabs/discovery-api v0.1.5/go.mod h1:b9jOm9T2puYVcRqCAjWxPcHz2qBqDX8I0OZDOyOFHXg= github.com/siderolabs/discovery-client v0.1.10 h1:bTAvFLiISSzVXyYL1cIgAz8cPYd9ZfvhxwdebgtxARA= @@ -775,7 +709,6 @@ github.com/smira/kobject v0.0.0-20240304111826-49c8d4613389/go.mod h1:+SexPO1Zvd github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= -github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -797,11 +730,12 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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/thejerf/suture/v4 v4.0.5 h1:F1E/4FZwXWqvlWDKEUo6/ndLtxGAUzMmNqkrMknZbAA= +github.com/thejerf/suture/v4 v4.0.5/go.mod h1:gu9Y4dXNUWFrByqRt30Rm9/UZ0wzRSt9AJS6xu/ZGxU= github.com/u-root/u-root v0.14.0 h1:Ka4T10EEML7dQ5XDvO9c3MBN8z4nuSnGjcd1jmU2ivg= github.com/u-root/u-root v0.14.0/go.mod h1:hAyZorapJe4qzbLWlAkmSVCJGbfoU9Pu4jpJ1WMluqE= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= @@ -812,12 +746,8 @@ github.com/unix4ever/yaml v0.0.0-20220527175918-f17b0f05cf2c h1:Vn6nVVu9MdOYvXPk github.com/unix4ever/yaml v0.0.0-20220527175918-f17b0f05cf2c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= github.com/urfave/cli v1.19.1/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= -github.com/urfave/cli v1.22.15 h1:nuqt+pdC/KqswQKhETJjo7pvn/k4xMUxgW6liI7XpnM= -github.com/urfave/cli v1.22.15/go.mod h1:wSan1hmo5zeyLGBjRJbzRTNk8gwoYa2B9n4q9dmRIc0= github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= -github.com/vburenin/ifacemaker v1.2.1 h1:3Vq8B/bfBgjWTkv+jDg4dVL1KHt3k1K4lO7XRxYA2sk= -github.com/vburenin/ifacemaker v1.2.1/go.mod h1:5WqrzX2aD7/hi+okBjcaEQJMg4lDGrpuEX3B8L4Wgrs= github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk= github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= @@ -905,7 +835,6 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -958,8 +887,6 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1041,7 +968,6 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -1052,8 +978,6 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= @@ -1066,12 +990,9 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1165,7 +1086,6 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1217,12 +1137,8 @@ k8s.io/pod-security-admission v0.32.0-alpha.3 h1:5t6eLvBEEiLsB7XIMAGFzBQhPnA7zeb k8s.io/pod-security-admission v0.32.0-alpha.3/go.mod h1:HOqwkmey6Z/Rc1FV5wXLUUSnZ2thBvw0MyW/gLd5Xuc= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -kernel.org/pub/linux/libs/security/libcap/cap v1.2.70 h1:QnLPkuDWWbD5C+3DUA2IUXai5TK6w2zff+MAGccqdsw= -kernel.org/pub/linux/libs/security/libcap/cap v1.2.70/go.mod h1:/iBwcj9nbLejQitYvUm9caurITQ6WyNHibJk6Q9fiS4= kernel.org/pub/linux/libs/security/libcap/cap v1.2.71 h1:M/uExTYLPOeX6EGZ3PGeCD4pbbvpdF8TyU1X8/b51oU= kernel.org/pub/linux/libs/security/libcap/cap v1.2.71/go.mod h1:p6YwxVCVAgpn8NL1FiZOnoSuwqaGBDdWeb3suWEDUxM= -kernel.org/pub/linux/libs/security/libcap/psx v1.2.70 h1:HsB2G/rEQiYyo1bGoQqHZ/Bvd6x1rERQTNdPr1FyWjI= -kernel.org/pub/linux/libs/security/libcap/psx v1.2.70/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24= kernel.org/pub/linux/libs/security/libcap/psx v1.2.71 h1:i19+O6oaKRqgflRO4o7WKdU8LJ7vKNSFLDDqHB6CvQ8= kernel.org/pub/linux/libs/security/libcap/psx v1.2.71/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24= rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= diff --git a/internal/app/machined/pkg/controllers/network/dns_resolve_cache.go b/internal/app/machined/pkg/controllers/network/dns_resolve_cache.go index 98210340ba7..af14451329b 100644 --- a/internal/app/machined/pkg/controllers/network/dns_resolve_cache.go +++ b/internal/app/machined/pkg/controllers/network/dns_resolve_cache.go @@ -5,24 +5,20 @@ package network import ( + "cmp" "context" - "errors" "fmt" - "net" + "iter" "net/netip" - "slices" - "strings" "sync" - "time" "github.com/coredns/coredns/plugin/pkg/proxy" "github.com/cosi-project/runtime/pkg/controller" "github.com/cosi-project/runtime/pkg/safe" "github.com/cosi-project/runtime/pkg/state" - dnssrv "github.com/miekg/dns" "github.com/siderolabs/gen/optional" - "github.com/siderolabs/gen/pair" "github.com/siderolabs/gen/xiter" + "github.com/thejerf/suture/v4" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -36,13 +32,9 @@ type DNSResolveCacheController struct { State state.State Logger *zap.Logger - mx sync.Mutex - handler *dns.Handler - nodeHandler *dns.NodeHandler - rootHandler dnssrv.Handler - runners map[runnerConfig]pair.Pair[func(), <-chan struct{}] - reconcile chan struct{} - originalCtx context.Context //nolint:containedctx + mx sync.Mutex + manager *dns.Manager + reconcile chan struct{} } // Name implements controller.Controller interface. @@ -74,15 +66,21 @@ func (ctrl *DNSResolveCacheController) Outputs() []controller.Output { } // Run implements controller.Controller interface. -// -//nolint:gocyclo,cyclop -func (ctrl *DNSResolveCacheController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error { +func (ctrl *DNSResolveCacheController) Run(ctx context.Context, r controller.Runtime, _ *zap.Logger) error { ctrl.init(ctx) ctrl.mx.Lock() defer ctrl.mx.Unlock() - defer ctrl.stopRunners(ctx, false) + defer func() { + if err := ctrl.manager.ClearAll(ctx.Err() == nil); err != nil { + ctrl.Logger.Error("error stopping dns runners", zap.Error(err)) + } + + if ctx.Err() != nil { + ctrl.Logger.Info("manager finished", zap.Error(<-ctrl.manager.Done())) + } + }() for { select { @@ -90,264 +88,124 @@ func (ctrl *DNSResolveCacheController) Run(ctx context.Context, r controller.Run return nil case <-r.EventCh(): case <-ctrl.reconcile: - for cfg, stop := range ctrl.runners { - select { - default: - continue - case <-stop.F2: - } - - stop.F1() - delete(ctrl.runners, cfg) - } } - cfg, err := safe.ReaderGetByID[*network.HostDNSConfig](ctx, r, network.HostDNSConfigID) - if err != nil { - if state.IsNotFoundError(err) { - continue - } - - return fmt.Errorf("error getting host dns config: %w", err) - } - - r.StartTrackingOutputs() - - if !cfg.TypedSpec().Enabled { - ctrl.stopRunners(ctx, true) - - if err = safe.CleanupOutputs[*network.DNSResolveCache](ctx, r); err != nil { - return fmt.Errorf("error cleaning up dns status on disable: %w", err) - } - - continue - } - - ctrl.nodeHandler.SetEnabled(cfg.TypedSpec().ResolveMemberNames) - - touchedRunners := make(map[runnerConfig]struct{}, len(ctrl.runners)) - - for _, addr := range cfg.TypedSpec().ListenAddresses { - for _, netwk := range []string{"udp", "tcp"} { - runnerCfg := runnerConfig{net: netwk, addr: addr} - - if _, ok := ctrl.runners[runnerCfg]; !ok { - runner, rErr := newDNSRunner(runnerCfg, ctrl.rootHandler, ctrl.Logger, cfg.TypedSpec().ServiceHostDNSAddress.IsValid()) - if rErr != nil { - return fmt.Errorf("error creating dns runner: %w", rErr) - } - - ctrl.runners[runnerCfg] = pair.MakePair(runner.Start(ctrl.handleDone(ctx, logger))) - } - - if err = ctrl.writeDNSStatus(ctx, r, runnerCfg); err != nil { - return fmt.Errorf("error writing dns status: %w", err) - } - - touchedRunners[runnerCfg] = struct{}{} - } - } - - for runnerCfg, stop := range ctrl.runners { - if _, ok := touchedRunners[runnerCfg]; !ok { - stop.F1() - delete(ctrl.runners, runnerCfg) - - continue - } - } - - upstreams, err := safe.ReaderListAll[*network.DNSUpstream](ctx, r) - if err != nil { - return fmt.Errorf("error getting resolver status: %w", err) - } - - prxs := xiter.Map( - // We are using iterator here to preserve finalizer on - func(upstream *network.DNSUpstream) *proxy.Proxy { - return upstream.TypedSpec().Value.Conn.Proxy().(*proxy.Proxy) - }, - upstreams.All(), - ) - - if ctrl.handler.SetProxy(prxs) { - ctrl.Logger.Info("updated dns server nameservers", zap.Array("addrs", addrsArr(upstreams))) - } - - if err = safe.CleanupOutputs[*network.DNSResolveCache](ctx, r); err != nil { - return fmt.Errorf("error cleaning up dns status: %w", err) + if err := ctrl.run(ctx, r); err != nil { + return err } } } -func (ctrl *DNSResolveCacheController) writeDNSStatus(ctx context.Context, r controller.Runtime, config runnerConfig) error { - return safe.WriterModify(ctx, r, network.NewDNSResolveCache(fmt.Sprintf("%s-%s", config.net, config.addr)), func(drc *network.DNSResolveCache) error { - drc.TypedSpec().Status = "running" - - return nil - }) -} +func (ctrl *DNSResolveCacheController) run(ctx context.Context, r controller.Runtime) (resErr error) { + r.StartTrackingOutputs() + defer cleanupOutputs(ctx, r, &resErr) -func (ctrl *DNSResolveCacheController) init(ctx context.Context) { - if ctrl.runners != nil { - if ctrl.originalCtx != ctx { - // This should not happen, but if it does, it's a bug. - panic("DNSResolveCacheController is called with a different context") - } + cfg, err := safe.ReaderGetByID[*network.HostDNSConfig](ctx, r, network.HostDNSConfigID) - return + switch { + case state.IsNotFoundError(err): + return nil + case err != nil: + return fmt.Errorf("error getting host dns config: %w", err) } - ctrl.originalCtx = ctx - ctrl.handler = dns.NewHandler(ctrl.Logger) - ctrl.nodeHandler = dns.NewNodeHandler(ctrl.handler, &stateMapper{state: ctrl.State}, ctrl.Logger) - ctrl.rootHandler = dns.NewCache(ctrl.nodeHandler, ctrl.Logger) - ctrl.runners = map[runnerConfig]pair.Pair[func(), <-chan struct{}]{} - ctrl.reconcile = make(chan struct{}, 1) - - // Ensure we stop all runners when the context is canceled, no matter where we are currently. - // For example if we are in Controller runtime sleeping after error and ctx is canceled, we should stop all runners - // but, we will never call Run method again, so we need to ensure this happens regardless of the current state. - context.AfterFunc(ctx, func() { - ctrl.mx.Lock() - defer ctrl.mx.Unlock() - - ctrl.stopRunners(ctx, true) - }) -} - -func (ctrl *DNSResolveCacheController) stopRunners(ctx context.Context, ignoreCtx bool) { - if !ignoreCtx && ctx.Err() == nil { - // context not yet canceled, preserve runners, cache and handler - return - } + ctrl.manager.AllowNodeResolving(cfg.TypedSpec().ResolveMemberNames) - for _, stop := range ctrl.runners { - stop.F1() + if !cfg.TypedSpec().Enabled { + return ctrl.manager.ClearAll(false) } - clear(ctrl.runners) - - ctrl.handler.Stop() -} + pairs := allAddressPairs(cfg.TypedSpec().ListenAddresses) + forwardKubeDNSToHost := cfg.TypedSpec().ServiceHostDNSAddress.IsValid() || cfg.TypedSpec().ServiceHostDNSAddressV6.IsValid() -func (ctrl *DNSResolveCacheController) handleDone(ctx context.Context, logger *zap.Logger) func(err error) { - return func(err error) { - if ctx.Err() != nil { - if err != nil && !errors.Is(err, net.ErrClosed) { - logger.Error("controller is closing, but error running dns server", zap.Error(err)) - } - - return - } - - if err != nil { - logger.Error("error running dns server", zap.Error(err)) + for runCfg, runErr := range ctrl.manager.RunAll(pairs, forwardKubeDNSToHost) { + switch { + case runErr != nil: + return fmt.Errorf("error updating dns runner '%v': %w", runCfg, runErr) + case runCfg.Status == dns.StatusRemoved: + // Removed runned, no reason to update status + continue } - select { - case ctrl.reconcile <- struct{}{}: - default: + if err = ctrl.writeDNSStatus(ctx, r, runCfg.AddressPair); err != nil { + return fmt.Errorf("error writing dns status: %w", err) } } -} - -type runnerConfig struct { - net string - addr netip.AddrPort -} -func newDNSRunner(cfg runnerConfig, rootHandler dnssrv.Handler, logger *zap.Logger, forwardEnabled bool) (*dns.Server, error) { - if cfg.addr.Addr().Is6() { - cfg.net += "6" - } - - logger = logger.With(zap.String("net", cfg.net), zap.Stringer("addr", cfg.addr)) - - var serverOpts dns.ServerOptions - - controlFn, ctrlErr := dns.MakeControl(cfg.net, forwardEnabled) - if ctrlErr != nil { - return nil, fmt.Errorf("error creating %q control function: %w", cfg.net, ctrlErr) + upstreams, err := safe.ReaderListAll[*network.DNSUpstream](ctx, r) + if err != nil { + return fmt.Errorf("error getting resolver status: %w", err) } - switch cfg.net { - case "udp", "udp6": - packetConn, err := dns.NewUDPPacketConn(cfg.net, cfg.addr.String(), controlFn) - if err != nil { - return nil, fmt.Errorf("error creating %q packet conn: %w", cfg.net, err) - } - - serverOpts = dns.ServerOptions{ - PacketConn: packetConn, - Handler: rootHandler, - Logger: logger, - } - - case "tcp", "tcp6": - listener, err := dns.NewTCPListener(cfg.net, cfg.addr.String(), controlFn) - if err != nil { - return nil, fmt.Errorf("error creating %q listener: %w", cfg.net, err) - } + prxs := xiter.Map( + // We are using iterator here to preserve finalizer on + func(upstream *network.DNSUpstream) *proxy.Proxy { + return upstream.TypedSpec().Value.Conn.Proxy().(*proxy.Proxy) + }, + upstreams.All(), + ) - serverOpts = dns.ServerOptions{ - Listener: listener, - Handler: rootHandler, - ReadTimeout: 3 * time.Second, - WriteTimeout: 5 * time.Second, - IdleTimeout: func() time.Duration { return 10 * time.Second }, - MaxTCPQueries: -1, - Logger: logger, - } + if ctrl.manager.SetUpstreams(prxs) { + ctrl.Logger.Info("updated dns server nameservers", zap.Array("addrs", addrsArr(upstreams))) } - return dns.NewServer(serverOpts), nil + return nil } -type stateMapper struct { - state state.State +func cleanupOutputs(ctx context.Context, r controller.Runtime, resErr *error) { + if err := safe.CleanupOutputs[*network.DNSResolveCache](ctx, r); err != nil { + *resErr = cmp.Or(*resErr, fmt.Errorf("error cleaning up dns resolve cache: %w", err)) + } } -func (s *stateMapper) ResolveAddr(ctx context.Context, qType uint16, name string) []netip.Addr { - name = strings.TrimRight(name, ".") +func (ctrl *DNSResolveCacheController) writeDNSStatus(ctx context.Context, r controller.Runtime, config dns.AddressPair) error { + res := network.NewDNSResolveCache(fmt.Sprintf("%s-%s", config.Network, config.Addr)) - list, err := safe.ReaderListAll[*cluster.Member](ctx, s.state) - if err != nil { - return nil - } + return safe.WriterModify(ctx, r, res, func(drc *network.DNSResolveCache) error { + drc.TypedSpec().Status = "running" - elem, ok := list.Find(func(res *cluster.Member) bool { - return fqdnMatch(name, res.TypedSpec().Hostname) || fqdnMatch(name, res.Metadata().ID()) - }) - if !ok { return nil - } - - result := slices.DeleteFunc(slices.Clone(elem.TypedSpec().Addresses), func(addr netip.Addr) bool { - return !((qType == dnssrv.TypeA && addr.Is4()) || (qType == dnssrv.TypeAAAA && addr.Is6())) }) +} - if len(result) == 0 { - return nil +func (ctrl *DNSResolveCacheController) init(ctx context.Context) { + if ctrl.manager == nil { + ctrl.manager = dns.NewManager(&memberReader{st: ctrl.State}, ctrl.eventHook, ctrl.Logger) + + // Ensure we stop all runners when the context is canceled, no matter where we are currently. + // For example if we are in Controller runtime sleeping after error and ctx is canceled, we should stop all runners + // but, we will never call Run method again, so we need to ensure this happens regardless of the current state. + context.AfterFunc(ctx, func() { + ctrl.mx.Lock() + defer ctrl.mx.Unlock() + + if err := ctrl.manager.ClearAll(false); err != nil { + ctrl.Logger.Error("error ctx stopping dns runners", zap.Error(err)) + } + }) } - return result + ctrl.manager.ServeBackground(ctx) } -func fqdnMatch(what, where string) bool { - what = strings.TrimRight(what, ".") - where = strings.TrimRight(where, ".") +func (ctrl *DNSResolveCacheController) eventHook(event suture.Event) { + ctrl.Logger.Info("dns-resolve-cache-runners event", zap.String("event", event.String())) - if what == where { - return true + select { + case ctrl.reconcile <- struct{}{}: + default: } +} + +type memberReader struct{ st state.State } - first, _, found := strings.Cut(where, ".") - if !found { - return false +func (m *memberReader) ReadMembers(ctx context.Context) (iter.Seq[*cluster.Member], error) { + list, err := safe.ReaderListAll[*cluster.Member](ctx, m.st) + if err != nil { + return nil, err } - return what == first + return list.All(), nil } type addrsArr safe.List[*network.DNSUpstream] @@ -361,3 +219,23 @@ func (a addrsArr) MarshalLogArray(encoder zapcore.ArrayEncoder) error { return nil } + +func allAddressPairs(addresses []netip.AddrPort) iter.Seq[dns.AddressPair] { + return func(yield func(dns.AddressPair) bool) { + for _, addr := range addresses { + networks := [...]string{"udp", "tcp"} + if addr.Addr().Is6() { + networks = [...]string{"udp6", "tcp6"} + } + + for _, netwk := range networks { + if !yield(dns.AddressPair{ + Network: netwk, + Addr: addr, + }) { + return + } + } + } + } +} diff --git a/internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go b/internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go index bc71657701f..e183db994d8 100644 --- a/internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go +++ b/internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go @@ -285,6 +285,7 @@ func getDynamicPort() (string, error) { func makeAddrs(port string) []netip.AddrPort { return []netip.AddrPort{ netip.MustParseAddrPort("127.0.0.53:" + port), + netip.MustParseAddrPort("[::1]:" + port), } } diff --git a/internal/app/machined/pkg/controllers/network/etcfile.go b/internal/app/machined/pkg/controllers/network/etcfile.go index 60c5a863b58..fe864a28059 100644 --- a/internal/app/machined/pkg/controllers/network/etcfile.go +++ b/internal/app/machined/pkg/controllers/network/etcfile.go @@ -20,8 +20,8 @@ import ( "github.com/cosi-project/runtime/pkg/safe" "github.com/cosi-project/runtime/pkg/state" "github.com/siderolabs/gen/optional" - "github.com/siderolabs/gen/value" "github.com/siderolabs/gen/xiter" + "github.com/siderolabs/gen/xslices" "go.uber.org/zap" efiles "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/files" @@ -158,10 +158,13 @@ func (ctrl *EtcFileController) Run(ctx context.Context, r controller.Runtime, _ } if resolverStatus != nil && hostDNSCfg != nil { - dnsServers := resolverStatus.TypedSpec().DNSServers + dnsServers := xslices.FilterInPlace( + []netip.Addr{hostDNSCfg.TypedSpec().ServiceHostDNSAddress, hostDNSCfg.TypedSpec().ServiceHostDNSAddressV6}, + netip.Addr.IsValid, + ) - if !value.IsZero(hostDNSCfg.TypedSpec().ServiceHostDNSAddress) { - dnsServers = []netip.Addr{hostDNSCfg.TypedSpec().ServiceHostDNSAddress} + if len(dnsServers) == 0 { + dnsServers = resolverStatus.TypedSpec().DNSServers } conf := renderResolvConf(slices.All(dnsServers), hostnameStatusSpec, cfgProvider) diff --git a/internal/app/machined/pkg/controllers/network/etcfile_test.go b/internal/app/machined/pkg/controllers/network/etcfile_test.go index c6b849d222e..b9d5d1084b9 100644 --- a/internal/app/machined/pkg/controllers/network/etcfile_test.go +++ b/internal/app/machined/pkg/controllers/network/etcfile_test.go @@ -128,8 +128,10 @@ func (suite *EtcFileConfigSuite) SetupTest() { suite.hostDNSConfig.TypedSpec().ListenAddresses = []netip.AddrPort{ netip.MustParseAddrPort("127.0.0.53:53"), netip.MustParseAddrPort("169.254.116.108:53"), + netip.MustParseAddrPort("[fe80:5461:6c6f:7320:4f53:2044:4e53:1]:53"), } suite.hostDNSConfig.TypedSpec().ServiceHostDNSAddress = netip.MustParseAddr("169.254.116.108") + suite.hostDNSConfig.TypedSpec().ServiceHostDNSAddressV6 = netip.MustParseAddr("fe80:5461:6c6f:7320:4f53:2044:4e53:1") } func (suite *EtcFileConfigSuite) startRuntime() { @@ -228,7 +230,7 @@ func (suite *EtcFileConfigSuite) TestComplete() { etcFileContents{ hosts: "127.0.0.1 localhost\n33.11.22.44 foo.example.com foo\n::1 localhost ip6-localhost ip6-loopback\nff02::1 ip6-allnodes\nff02::2 ip6-allrouters\n10.0.0.1 a b\n10.0.0.2 c d\n", //nolint:lll resolvConf: "nameserver 127.0.0.53\n\nsearch example.com\n", - resolvGlobalConf: "nameserver 169.254.116.108\n\nsearch example.com\n", + resolvGlobalConf: "nameserver 169.254.116.108\nnameserver fe80:5461:6c6f:7320:4f53:2044:4e53:1\n\nsearch example.com\n", }, ) } @@ -239,7 +241,7 @@ func (suite *EtcFileConfigSuite) TestNoExtraHosts() { etcFileContents{ hosts: "127.0.0.1 localhost\n33.11.22.44 foo.example.com foo\n::1 localhost ip6-localhost ip6-loopback\nff02::1 ip6-allnodes\nff02::2 ip6-allrouters\n", resolvConf: "nameserver 127.0.0.53\n\nsearch example.com\n", - resolvGlobalConf: "nameserver 169.254.116.108\n\nsearch example.com\n", + resolvGlobalConf: "nameserver 169.254.116.108\nnameserver fe80:5461:6c6f:7320:4f53:2044:4e53:1\n\nsearch example.com\n", }, ) } @@ -262,7 +264,7 @@ func (suite *EtcFileConfigSuite) TestNoSearchDomain() { etcFileContents{ hosts: "127.0.0.1 localhost\n33.11.22.44 foo.example.com foo\n::1 localhost ip6-localhost ip6-loopback\nff02::1 ip6-allnodes\nff02::2 ip6-allrouters\n", resolvConf: "nameserver 127.0.0.53\n", - resolvGlobalConf: "nameserver 169.254.116.108\n", + resolvGlobalConf: "nameserver 169.254.116.108\nnameserver fe80:5461:6c6f:7320:4f53:2044:4e53:1\n", }, ) } @@ -275,7 +277,7 @@ func (suite *EtcFileConfigSuite) TestNoDomainname() { etcFileContents{ hosts: "127.0.0.1 localhost\n33.11.22.44 foo\n::1 localhost ip6-localhost ip6-loopback\nff02::1 ip6-allnodes\nff02::2 ip6-allrouters\n", resolvConf: "nameserver 127.0.0.53\n", - resolvGlobalConf: "nameserver 169.254.116.108\n", + resolvGlobalConf: "nameserver 169.254.116.108\nnameserver fe80:5461:6c6f:7320:4f53:2044:4e53:1\n", }, ) } @@ -286,7 +288,7 @@ func (suite *EtcFileConfigSuite) TestOnlyResolvers() { etcFileContents{ hosts: "", resolvConf: "nameserver 127.0.0.53\n", - resolvGlobalConf: "nameserver 169.254.116.108\n", + resolvGlobalConf: "nameserver 169.254.116.108\nnameserver fe80:5461:6c6f:7320:4f53:2044:4e53:1\n", }, ) } diff --git a/internal/app/machined/pkg/controllers/network/hostdns_config.go b/internal/app/machined/pkg/controllers/network/hostdns_config.go index 4fad1d70a1d..c093e4042c6 100644 --- a/internal/app/machined/pkg/controllers/network/hostdns_config.go +++ b/internal/app/machined/pkg/controllers/network/hostdns_config.go @@ -8,13 +8,13 @@ import ( "context" "fmt" "net/netip" + "slices" "github.com/cosi-project/runtime/pkg/controller" "github.com/cosi-project/runtime/pkg/resource" "github.com/cosi-project/runtime/pkg/safe" "github.com/cosi-project/runtime/pkg/state" "github.com/siderolabs/gen/optional" - "github.com/siderolabs/gen/value" "github.com/siderolabs/go-procfs/procfs" "go.uber.org/zap" @@ -83,7 +83,7 @@ func (ctrl *HostDNSConfigController) Run(ctx context.Context, r controller.Runti cfgProvider = cfg.Config() } - var newServiceAddr netip.Addr + newServiceAddrs := make([]netip.Addr, 0, 2) if err := safe.WriterModify(ctx, r, network.NewHostDNSConfig(network.HostDNSConfigID), func(res *network.HostDNSConfig) error { res.TypedSpec().ListenAddresses = []netip.AddrPort{ @@ -91,6 +91,7 @@ func (ctrl *HostDNSConfigController) Run(ctx context.Context, r controller.Runti } res.TypedSpec().ServiceHostDNSAddress = netip.Addr{} + res.TypedSpec().ServiceHostDNSAddressV6 = netip.Addr{} if cfgProvider == nil { res.TypedSpec().Enabled = false @@ -101,11 +102,30 @@ func (ctrl *HostDNSConfigController) Run(ctx context.Context, r controller.Runti res.TypedSpec().Enabled = cfgProvider.Machine().Features().HostDNS().Enabled() res.TypedSpec().ResolveMemberNames = cfgProvider.Machine().Features().HostDNS().ResolveMemberNames() - if cfgProvider.Machine().Features().HostDNS().ForwardKubeDNSToHost() { - newServiceAddr = netip.MustParseAddr(constants.HostDNSAddress) + if !cfgProvider.Machine().Features().HostDNS().ForwardKubeDNSToHost() { + return nil + } + + if slices.ContainsFunc( + cfgProvider.Cluster().Network().PodCIDRs(), + func(cidr string) bool { return netip.MustParsePrefix(cidr).Addr().Is4() }, + ) { + parsed := netip.MustParseAddr(constants.HostDNSAddress) + newServiceAddrs = append(newServiceAddrs, parsed) + + res.TypedSpec().ListenAddresses = append(res.TypedSpec().ListenAddresses, netip.AddrPortFrom(parsed, 53)) + res.TypedSpec().ServiceHostDNSAddress = parsed + } + + if slices.ContainsFunc( + cfgProvider.Cluster().Network().PodCIDRs(), + func(cidr string) bool { return netip.MustParsePrefix(cidr).Addr().Is6() }, + ) { + parsed := netip.MustParseAddr(constants.HostDNSAddressV6) + newServiceAddrs = append(newServiceAddrs, parsed) - res.TypedSpec().ListenAddresses = append(res.TypedSpec().ListenAddresses, netip.AddrPortFrom(newServiceAddr, 53)) - res.TypedSpec().ServiceHostDNSAddress = newServiceAddr + res.TypedSpec().ListenAddresses = append(res.TypedSpec().ListenAddresses, netip.AddrPortFrom(parsed, 53)) + res.TypedSpec().ServiceHostDNSAddressV6 = parsed } return nil @@ -113,24 +133,22 @@ func (ctrl *HostDNSConfigController) Run(ctx context.Context, r controller.Runti return fmt.Errorf("error writing host dns config: %w", err) } - var touched *network.AddressSpec + touched := make([]*network.AddressSpec, 0, 2) - if !value.IsZero(newServiceAddr) { - touched, err = updateSpec(ctx, r, newServiceAddr, logger) + for _, newServiceAddr := range newServiceAddrs { + res, err := updateSpec(ctx, r, newServiceAddr, logger) if err != nil { return err } + + touched = append(touched, res) } if err = ctrl.cleanupAddressSpecs( ctx, r, func(id resource.ID) bool { - if touched == nil { - return false - } - - return id == touched.Metadata().ID() + return slices.ContainsFunc(touched, func(r *network.AddressSpec) bool { return id == r.Metadata().ID() }) }, logger, ); err != nil { diff --git a/internal/app/machined/pkg/controllers/network/nftables_chain_config.go b/internal/app/machined/pkg/controllers/network/nftables_chain_config.go index cc10e909945..184bcbd1e69 100644 --- a/internal/app/machined/pkg/controllers/network/nftables_chain_config.go +++ b/internal/app/machined/pkg/controllers/network/nftables_chain_config.go @@ -19,6 +19,7 @@ import ( "github.com/siderolabs/go-pointer" "go.uber.org/zap" + cfg "github.com/siderolabs/talos/pkg/machinery/config/config" "github.com/siderolabs/talos/pkg/machinery/constants" "github.com/siderolabs/talos/pkg/machinery/nethelpers" "github.com/siderolabs/talos/pkg/machinery/resources/config" @@ -154,8 +155,6 @@ func (ctrl *NfTablesChainConfigController) Run(ctx context.Context, r controller if cfg.Config().Machine() != nil && cfg.Config().Cluster() != nil { if cfg.Config().Machine().Features().HostDNS().ForwardKubeDNSToHost() { - hostDNSIP := netip.MustParseAddr(constants.HostDNSAddress) - // allow traffic to host DNS for _, protocol := range []nethelpers.Protocol{nethelpers.ProtocolUDP, nethelpers.ProtocolTCP} { spec.Rules = append(spec.Rules, @@ -170,7 +169,7 @@ func (ctrl *NfTablesChainConfigController) Run(ctx context.Context, r controller ), }, MatchDestinationAddress: &network.NfTablesAddressMatch{ - IncludeSubnets: []netip.Prefix{netip.PrefixFrom(hostDNSIP, hostDNSIP.BitLen())}, + IncludeSubnets: hostDNSSubnets(cfg.Config().Cluster().Network()), }, MatchLayer4: &network.NfTablesLayer4Match{ Protocol: protocol, @@ -256,3 +255,20 @@ func (ctrl *NfTablesChainConfigController) Run(ctx context.Context, r controller } } } + +func hostDNSSubnets(clusterNetwork cfg.ClusterNetwork) []netip.Prefix { + result := []netip.Addr{hostDNSIPv4} + + for _, podCIDR := range clusterNetwork.PodCIDRs() { + if netip.MustParsePrefix(podCIDR).Addr().Is6() { + result = append(result, hostDNSIPv6) + } + } + + return xslices.Map(result, func(a netip.Addr) netip.Prefix { return netip.PrefixFrom(a, a.BitLen()) }) +} + +var ( + hostDNSIPv4 = netip.MustParseAddr(constants.HostDNSAddress) + hostDNSIPv6 = netip.MustParseAddr(constants.HostDNSAddressV6) +) diff --git a/internal/app/machined/pkg/xcontext/xcontext.go b/internal/app/machined/pkg/xcontext/xcontext.go new file mode 100644 index 00000000000..32dca45de85 --- /dev/null +++ b/internal/app/machined/pkg/xcontext/xcontext.go @@ -0,0 +1,28 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package xcontext provides a small utils for context package +package xcontext + +import "context" + +// AfterFuncSync is like [context.AfterFunc] but it blocks until the function is executed. +func AfterFuncSync(ctx context.Context, fn func()) func() bool { + stopChan := make(chan struct{}) + + stop := context.AfterFunc(ctx, func() { + defer close(stopChan) + + fn() + }) + + return func() bool { + result := stop() + if !result { + <-stopChan + } + + return result + } +} diff --git a/internal/pkg/dns/dns.go b/internal/pkg/dns/dns.go index 6a8be932fcc..5eaa7619f4d 100644 --- a/internal/pkg/dns/dns.go +++ b/internal/pkg/dns/dns.go @@ -9,12 +9,10 @@ import ( "context" "errors" "fmt" - "io" "iter" "net" "net/netip" "slices" - "strings" "sync" "sync/atomic" "syscall" @@ -207,7 +205,7 @@ func NewNodeHandler(next plugin.Handler, hostMapper HostMapper, logger *zap.Logg // HostMapper is a name to node mapper. type HostMapper interface { - ResolveAddr(ctx context.Context, qType uint16, name string) []netip.Addr + ResolveAddr(ctx context.Context, qType uint16, name string) (iter.Seq[netip.Addr], bool) } // NodeHandler try to resolve dns request to a node. If required node is not found, it will move to the next handler. @@ -238,14 +236,19 @@ func (h *NodeHandler) ServeDNS(ctx context.Context, wrt dns.ResponseWriter, msg req := request.Request{W: wrt, Req: msg} // Check if the request is for a node. - result := h.mapper.ResolveAddr(ctx, req.QType(), req.Name()) - if len(result) == 0 { + result, ok := h.mapper.ResolveAddr(ctx, req.QType(), req.Name()) + if !ok { + return h.next.ServeDNS(ctx, wrt, msg) + } + + answers := mapAnswers(result, req.Name()) + if len(answers) == 0 { return h.next.ServeDNS(ctx, wrt, msg) } resp := new(dns.Msg).SetReply(req.Req) resp.Authoritative = true - resp.Answer = mapAnswers(result, req.Name()) + resp.Answer = answers err := wrt.WriteMsg(resp) if err != nil { @@ -256,10 +259,10 @@ func (h *NodeHandler) ServeDNS(ctx context.Context, wrt dns.ResponseWriter, msg return dns.RcodeSuccess, nil } -func mapAnswers(addrs []netip.Addr, name string) []dns.RR { +func mapAnswers(addrs iter.Seq[netip.Addr], name string) []dns.RR { var result []dns.RR - for _, addr := range addrs { + for addr := range addrs { switch { case addr.Is4(): result = append(result, &dns.A{ @@ -295,89 +298,6 @@ func (h *NodeHandler) SetEnabled(enabled bool) { h.enabled.Store(enabled) } -// ServerOptions is a Server options. -type ServerOptions struct { - Listener net.Listener - PacketConn net.PacketConn - Handler dns.Handler - ReadTimeout time.Duration - WriteTimeout time.Duration - IdleTimeout func() time.Duration - MaxTCPQueries int - Logger *zap.Logger -} - -// NewServer creates a new Server. -func NewServer(opts ServerOptions) *Server { - return &Server{ - srv: &dns.Server{ - Listener: opts.Listener, - PacketConn: opts.PacketConn, - Handler: opts.Handler, - UDPSize: dns.DefaultMsgSize, // 4096 since default is [dns.MinMsgSize] = 512 bytes, which is too small. - ReadTimeout: opts.ReadTimeout, - WriteTimeout: opts.WriteTimeout, - IdleTimeout: opts.IdleTimeout, - MaxTCPQueries: opts.MaxTCPQueries, - }, - logger: opts.Logger, - } -} - -// Server is a dns server. -type Server struct { - srv *dns.Server - logger *zap.Logger -} - -// Start starts the dns server. Returns a function to stop the server. -func (s *Server) Start(onDone func(err error)) (stop func(), stopped <-chan struct{}) { - done := make(chan struct{}) - - fn := sync.OnceFunc(func() { - for { - err := s.srv.Shutdown() - if err != nil { - if strings.Contains(err.Error(), "server not started") { - // There a possible scenario where `go func()` not yet reached `ActivateAndServe` and yielded CPU - // time to another goroutine and then this closure reached `Shutdown`. In that case - // `ActivateAndServe` will actually start after `Shutdown` and this closure will block forever - // because `go func()` will never exit and close `done` channel. - continue - } - - s.logger.Error("error shutting down dns server", zap.Error(err)) - } - - break - } - - closer := io.Closer(s.srv.Listener) - if closer == nil { - closer = s.srv.PacketConn - } - - if closer != nil { - err := closer.Close() - if err != nil && !errors.Is(err, net.ErrClosed) { - s.logger.Error("error closing dns server listener", zap.Error(err)) - } else { - s.logger.Debug("dns server listener closed") - } - } - - <-done - }) - - go func() { - defer close(done) - - onDone(s.srv.ActivateAndServe()) - }() - - return fn, done -} - // NewTCPListener creates a new TCP listener. func NewTCPListener(network, addr string, control ControlFn) (net.Listener, error) { network, ok := networkNames[network] diff --git a/internal/pkg/dns/dns_test.go b/internal/pkg/dns/dns_test.go index e15ac7992e2..66d94fd801d 100644 --- a/internal/pkg/dns/dns_test.go +++ b/internal/pkg/dns/dns_test.go @@ -6,22 +6,26 @@ package dns_test import ( "context" + "iter" "net" "net/netip" + "runtime" "slices" + "strings" "testing" "time" "github.com/coredns/coredns/plugin/pkg/proxy" dnssrv "github.com/miekg/dns" - "github.com/siderolabs/gen/ensure" - "github.com/siderolabs/gen/xiter" + "github.com/siderolabs/gen/maps" "github.com/siderolabs/gen/xslices" "github.com/siderolabs/gen/xtesting/check" "github.com/stretchr/testify/require" + "github.com/thejerf/suture/v4" "go.uber.org/zap/zaptest" "github.com/siderolabs/talos/internal/pkg/dns" + "github.com/siderolabs/talos/pkg/machinery/resources/cluster" ) func TestDNS(t *testing.T) { @@ -76,47 +80,98 @@ func TestDNS(t *testing.T) { }, } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - stop := newServer(t, test.nameservers...) - t.Cleanup(stop) + for _, dnsAddr := range []string{"127.0.0.1:10700", "[::1]:10700"} { + for _, test := range tests { + t.Run(dnsAddr+"/"+test.name, func(t *testing.T) { + stop := newManager(t, test.nameservers...) + t.Cleanup(stop) - time.Sleep(10 * time.Millisecond) + time.Sleep(10 * time.Millisecond) - r, err := dnssrv.Exchange(createQuery(test.hostname), "127.0.0.53:10700") - test.errCheck(t, err) + r, err := dnssrv.Exchange(createQuery(test.hostname), dnsAddr) + test.errCheck(t, err) - if r != nil { - require.Equal(t, test.expectedCode, r.Rcode, r) - } + if r != nil { + require.Equal(t, test.expectedCode, r.Rcode, r) + } - t.Logf("r: %s", r) - }) + t.Logf("r: %s", r) + }) + } } } func TestDNSEmptyDestinations(t *testing.T) { - stop := newServer(t) + stop := newManager(t) defer stop() time.Sleep(10 * time.Millisecond) - r, err := dnssrv.Exchange(createQuery("google.com"), "127.0.0.53:10700") + r, err := dnssrv.Exchange(createQuery("google.com"), "127.0.0.1:10700") + require.NoError(t, err) + require.Equal(t, dnssrv.RcodeServerFailure, r.Rcode, r) + + r, err = dnssrv.Exchange(createQuery("google.com"), "127.0.0.1:10700") + require.NoError(t, err) + require.Equal(t, dnssrv.RcodeServerFailure, r.Rcode, r) + + r, err = dnssrv.Exchange(createQuery("google.com"), "[::1]:10700") require.NoError(t, err) require.Equal(t, dnssrv.RcodeServerFailure, r.Rcode, r) - r, err = dnssrv.Exchange(createQuery("google.com"), "127.0.0.53:10700") + r, err = dnssrv.Exchange(createQuery("google.com"), "[::1]:10700") require.NoError(t, err) require.Equal(t, dnssrv.RcodeServerFailure, r.Rcode, r) stop() } -func newServer(t *testing.T, nameservers ...string) func() { - l := zaptest.NewLogger(t) +func TestGC_NOGC(t *testing.T) { + tests := map[string]bool{ + "ClearAll": false, + "No ClearAll": true, + } + + for name, f := range tests { + t.Run(name, func(t *testing.T) { + m := dns.NewManager(&testReader{}, func(e suture.Event) { t.Log("dns-runners event:", e) }, zaptest.NewLogger(t)) + + m.ServeBackground(context.Background()) + m.ServeBackground(context.Background()) + require.Panics(t, func() { m.ServeBackground(context.TODO()) }) + + for _, err := range m.RunAll(slices.Values([]dns.AddressPair{ + {Network: "udp", Addr: netip.MustParseAddrPort("127.0.0.1:10700")}, + {Network: "udp", Addr: netip.MustParseAddrPort("127.0.0.1:10701")}, + {Network: "udp", Addr: netip.MustParseAddrPort("[::1]:10700")}, + {Network: "udp", Addr: netip.MustParseAddrPort("[::1]:10701")}, + }), false) { + require.NoError(t, err) + } + + require.NoError(t, m.ClearAll(f)) + + m = nil + + for range 100 { + runtime.GC() + } + }) + } +} + +func newManager(t *testing.T, nameservers ...string) func() { + m := dns.NewManager(&testReader{}, func(e suture.Event) { + t.Log("dns-runners event:", e) + }, zaptest.NewLogger(t)) + + m.AllowNodeResolving(true) - handler := dns.NewHandler(l) - t.Cleanup(handler.Stop) + t.Cleanup(func() { + if err := m.ClearAll(false); err != nil { + t.Logf("error stopping dns runners: %v", err) + } + }) pxs := xslices.Map(nameservers, func(ns string) *proxy.Proxy { p := proxy.NewProxy(ns, net.JoinHostPort(ns, "53"), "dns") @@ -127,30 +182,44 @@ func newServer(t *testing.T, nameservers ...string) func() { return p }) - handler.SetProxy(xiter.Values(slices.All(pxs))) + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) - pc, err := dns.NewUDPPacketConn("udp", "127.0.0.53:10700", ensure.Value(dns.MakeControl("udp", false))) - require.NoError(t, err) + m.SetUpstreams(slices.Values(pxs)) - nodeHandler := dns.NewNodeHandler(handler, &testResolver{}, l) + m.ServeBackground(ctx) + m.ServeBackground(ctx) - nodeHandler.SetEnabled(true) + for _, err := range m.RunAll(slices.Values([]dns.AddressPair{ + {Network: "udp", Addr: netip.MustParseAddrPort("127.0.0.1:10700")}, + {Network: "udp", Addr: netip.MustParseAddrPort("127.0.0.1:10701")}, + {Network: "tcp", Addr: netip.MustParseAddrPort("127.0.0.1:10700")}, + }), false) { + if err != nil && strings.Contains(err.Error(), "failed to set TCP_FASTOPEN") { + continue + } - srv := dns.NewServer(dns.ServerOptions{ - PacketConn: pc, - Handler: dns.NewCache(nodeHandler, l), - Logger: l, - }) + require.NoError(t, err) + } - stop, _ := srv.Start(func(err error) { - if err != nil { - t.Errorf("error running dns server: %v", err) + for _, err := range m.RunAll(slices.Values([]dns.AddressPair{ + {Network: "udp", Addr: netip.MustParseAddrPort("127.0.0.1:10700")}, + {Network: "tcp", Addr: netip.MustParseAddrPort("127.0.0.1:10700")}, + {Network: "udp", Addr: netip.MustParseAddrPort("[::1]:10700")}, + {Network: "tcp", Addr: netip.MustParseAddrPort("[::1]:10700")}, + }), false) { + if err != nil && strings.Contains(err.Error(), "failed to set TCP_FASTOPEN") { + continue } - t.Logf("dns server stopped") - }) + require.NoError(t, err) + } - return stop + return func() { + if err := m.ClearAll(false); err != nil { + t.Logf("error stopping dns runners: %v", err) + } + } } func createQuery(name string) *dnssrv.Msg { @@ -169,19 +238,21 @@ func createQuery(name string) *dnssrv.Msg { } } -type testResolver struct{} +type testReader struct{} -func (*testResolver) ResolveAddr(_ context.Context, qType uint16, name string) []netip.Addr { - if qType != dnssrv.TypeA { - return nil +func (r *testReader) ReadMembers(context.Context) (iter.Seq[*cluster.Member], error) { + namesToAddresses := map[string][]netip.Addr{ + "talos-default-controlplane-1": {netip.MustParseAddr("172.20.0.2")}, + "talos-default-worker-1": {netip.MustParseAddr("172.20.0.3")}, } - switch name { - case "talos-default-controlplane-1.": - return []netip.Addr{netip.MustParseAddr("172.20.0.2")} - case "talos-default-worker-1.": - return []netip.Addr{netip.MustParseAddr("172.20.0.3")} - default: - return nil - } + result := maps.ToSlice(namesToAddresses, func(k string, v []netip.Addr) *cluster.Member { + result := cluster.NewMember(cluster.NamespaceName, k) + + result.TypedSpec().Addresses = v + + return result + }) + + return slices.Values(result), nil } diff --git a/internal/pkg/dns/manager.go b/internal/pkg/dns/manager.go new file mode 100644 index 00000000000..bd8510ccc01 --- /dev/null +++ b/internal/pkg/dns/manager.go @@ -0,0 +1,318 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package dns + +import ( + "context" + "errors" + "fmt" + "iter" + "net/netip" + "runtime" + "slices" + "strings" + "time" + + "github.com/coredns/coredns/plugin/pkg/proxy" + "github.com/hashicorp/go-multierror" + dnssrv "github.com/miekg/dns" + "github.com/siderolabs/gen/xiter" + "github.com/thejerf/suture/v4" + "go.uber.org/zap" + + "github.com/siderolabs/talos/pkg/machinery/resources/cluster" +) + +// ErrCreatingRunner is an error that occurs when creating a runner. +var ErrCreatingRunner = errors.New("error creating runner") + +// Manager manages DNS runners. +type Manager struct { + originalCtx context.Context //nolint:containedctx + handler *Handler + nodeHandler *NodeHandler + rootHandler *Cache + s *suture.Supervisor + supervisorCh <-chan error + logger *zap.Logger + runners map[AddressPair]suture.ServiceToken +} + +// NewManager creates a new manager. +func NewManager(mr MemberReader, hook suture.EventHook, logger *zap.Logger) *Manager { + handler := NewHandler(logger) + nodeHandler := NewNodeHandler(handler, &addrResolver{mr: mr}, logger) + rootHandler := NewCache(nodeHandler, logger) + + m := &Manager{ + handler: handler, + nodeHandler: nodeHandler, + rootHandler: rootHandler, + s: suture.New("dns-resolve-cache-runners", suture.Spec{EventHook: hook}), + logger: logger, + runners: map[AddressPair]suture.ServiceToken{}, + } + + // If we lost ref to the manager. Ensure finalizer is called and all upstreams are collected. + runtime.SetFinalizer(m, (*Manager).finalize) + + return m +} + +// ServeBackground starts the manager in the background. It panics if the manager is not initialized or if it's called +// more than once. +func (m *Manager) ServeBackground(ctx context.Context) { + switch { + case m.originalCtx == nil: + m.originalCtx = ctx + case m.originalCtx != ctx: + panic("Manager.ServeBackground is called with a different context") + case m.originalCtx == ctx: + return + } + + m.supervisorCh = m.s.ServeBackground(ctx) +} + +// AddressPair represents a network and address with port. +type AddressPair struct { + Network string + Addr netip.AddrPort +} + +// String returns a string representation of the address pair. +func (a AddressPair) String() string { return "Network: " + a.Network + ", Addr: " + a.Addr.String() } + +// RunAll updates and run the runners managed by the manager. It returns an iterator which yields the address pairs for +// all running and attempted ro run configurations. It's mandatory to range over the iterator to ensure all runners are updated. +func (m *Manager) RunAll(pairs iter.Seq[AddressPair], forwardEnabled bool) iter.Seq2[RunResult, error] { + return func(yield func(RunResult, error) bool) { + preserve := make(map[AddressPair]struct{}, len(m.runners)) + + for cfg := range pairs { + preserve[cfg] = struct{}{} + + if _, ok := m.runners[cfg]; ok { + if !yield(makeResult(cfg, StatusRunning), nil) { + return + } + + continue + } + + opts, err := newDNSRunnerOpts(cfg, m.rootHandler, forwardEnabled) + if err != nil { + err = fmt.Errorf("%w: %w", ErrCreatingRunner, err) + } else { + m.runners[cfg] = m.s.Add(NewRunner(opts, m.logger)) + } + + if !yield(makeResult(cfg, StatusNew), err) { + return + } + } + + for cfg, token := range m.runners { + if _, ok := preserve[cfg]; ok { + continue + } + + err := m.s.RemoveAndWait(token, 0) + if err != nil { + err = fmt.Errorf("error removing runner: %w", err) + } + + if !yield(makeResult(cfg, StatusRemoved), err) { + return + } + + delete(m.runners, cfg) + } + } +} + +func makeResult(cfg AddressPair, s Status) RunResult { return RunResult{AddressPair: cfg, Status: s} } + +// AllowNodeResolving enables or disables the node resolving feature. +func (m *Manager) AllowNodeResolving(enabled bool) { m.nodeHandler.SetEnabled(enabled) } + +// SetUpstreams sets the upstreams for the DNS handler. It returns true if the upstreams were updated, false otherwise. +func (m *Manager) SetUpstreams(prxs iter.Seq[*proxy.Proxy]) bool { return m.handler.SetProxy(prxs) } + +// ClearAll stops and removes all runners. It returns an iterator which yields the address pairs that were removed +// and/or errors that occurred during the removal process. It's mandatory to range over the iterator to ensure all +// runners are stopped. +func (m *Manager) ClearAll(dry bool) error { + if dry { + return nil + } + + var multiErr *multierror.Error + + for _, err := range m.clearAll() { + if err != nil { + multiErr = multierror.Append(multiErr, err) + } + } + + return multiErr.ErrorOrNil() +} + +func (m *Manager) clearAll() iter.Seq2[AddressPair, error] { + return func(yield func(AddressPair, error) bool) { + if len(m.runners) == 0 { + return + } + + defer m.handler.Stop() + + removeAndWait := m.s.RemoveAndWait + if m.originalCtx.Err() != nil { + // ctx canceled, no reason to remove runners from Supervisor since they are already dropped + removeAndWait = func(id suture.ServiceToken, timeout time.Duration) error { return nil } + } + + for runData, token := range m.runners { + err := removeAndWait(token, 0) + if err != nil { + err = fmt.Errorf("error removing runner: %w", err) + } + + if !yield(runData, err) { + return + } + + delete(m.runners, runData) + } + } +} + +func (m *Manager) finalize() { + for data, err := range m.clearAll() { + if err != nil { + m.logger.Error("error stopping dns runner", zap.Error(err)) + } + + m.logger.Info( + "dns runner stopped from finalizer!", + zap.String("address", data.Addr.String()), + zap.String("network", data.Network), + ) + } +} + +// Done reports if superwisor finished execution. +func (m *Manager) Done() <-chan error { + return m.supervisorCh +} + +type addrResolver struct { + mr MemberReader +} + +func (s *addrResolver) ResolveAddr(ctx context.Context, qType uint16, name string) (iter.Seq[netip.Addr], bool) { + name = strings.TrimRight(name, ".") + + items, err := s.mr.ReadMembers(ctx) + if err != nil { + return nil, false + } + + found, ok := xiter.Find(func(res *cluster.Member) bool { + return fqdnMatch(name, res.TypedSpec().Hostname) || fqdnMatch(name, res.Metadata().ID()) + }, items) + if !ok { + return nil, false + } + + return xiter.Filter( + func(addr netip.Addr) bool { + return (qType == dnssrv.TypeA && addr.Is4()) || (qType == dnssrv.TypeAAAA && addr.Is6()) + }, + slices.Values(found.TypedSpec().Addresses), + ), true +} + +func fqdnMatch(what, where string) bool { + what = strings.TrimRight(what, ".") + where = strings.TrimRight(where, ".") + + if what == where { + return true + } + + first, _, found := strings.Cut(where, ".") + if !found { + return false + } + + return what == first +} + +// MemberReader is an interface to read members. +type MemberReader interface { + ReadMembers(ctx context.Context) (iter.Seq[*cluster.Member], error) +} + +func newDNSRunnerOpts(cfg AddressPair, rootHandler dnssrv.Handler, forwardEnabled bool) (RunnerOptions, error) { + if cfg.Addr.Addr().Is6() && !strings.HasSuffix(cfg.Network, "6") { + cfg.Network += "6" + } + + var serverOpts RunnerOptions + + controlFn, ctrlErr := MakeControl(cfg.Network, forwardEnabled) + if ctrlErr != nil { + return serverOpts, fmt.Errorf("error creating %q control function: %w", cfg.Network, ctrlErr) + } + + switch cfg.Network { + case "udp", "udp6": + packetConn, err := NewUDPPacketConn(cfg.Network, cfg.Addr.String(), controlFn) + if err != nil { + return serverOpts, fmt.Errorf("error creating %q packet conn: %w", cfg.Network, err) + } + + serverOpts = RunnerOptions{ + PacketConn: packetConn, + Handler: rootHandler, + } + + case "tcp", "tcp6": + listener, err := NewTCPListener(cfg.Network, cfg.Addr.String(), controlFn) + if err != nil { + return serverOpts, fmt.Errorf("error creating %q listener: %w", cfg.Network, err) + } + + serverOpts = RunnerOptions{ + Listener: listener, + Handler: rootHandler, + ReadTimeout: 3 * time.Second, + WriteTimeout: 5 * time.Second, + IdleTimeout: func() time.Duration { return 10 * time.Second }, + MaxTCPQueries: -1, + } + } + + return serverOpts, nil +} + +// RunResult represents the result of a RunAll iteration. +type RunResult struct { + AddressPair + Status Status +} + +// Status represents the status of a runner. +type Status int + +const ( + // StatusNew represents a new runner. + StatusNew Status = iota + // StatusRunning represents a already running runner. + StatusRunning + // StatusRemoved represents a removed runner. + StatusRemoved +) diff --git a/internal/pkg/dns/runnner.go b/internal/pkg/dns/runnner.go new file mode 100644 index 00000000000..f0725de97d7 --- /dev/null +++ b/internal/pkg/dns/runnner.go @@ -0,0 +1,108 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package dns + +import ( + "context" + "errors" + "io" + "net" + "strings" + "time" + + "github.com/miekg/dns" + "go.uber.org/zap" + + "github.com/siderolabs/talos/internal/app/machined/pkg/xcontext" +) + +// RunnerOptions is a [Runner] options. +type RunnerOptions struct { + Listener net.Listener + PacketConn net.PacketConn + Handler dns.Handler + ReadTimeout time.Duration + WriteTimeout time.Duration + IdleTimeout func() time.Duration + MaxTCPQueries int +} + +// NewRunner creates a new [Runner]. +func NewRunner(opts RunnerOptions, l *zap.Logger) *Runner { + return &Runner{ + srv: &dns.Server{ + Listener: opts.Listener, + PacketConn: opts.PacketConn, + Handler: opts.Handler, + UDPSize: dns.DefaultMsgSize, // 4096 since default is [dns.MinMsgSize] = 512 bytes, which is too small. + ReadTimeout: opts.ReadTimeout, + WriteTimeout: opts.WriteTimeout, + IdleTimeout: opts.IdleTimeout, + MaxTCPQueries: opts.MaxTCPQueries, + }, + logger: l, + } +} + +// Runner is a DNS server runner. +type Runner struct { + srv *dns.Server + logger *zap.Logger +} + +// Serve starts the DNS server. +func (r *Runner) Serve(ctx context.Context) error { + detach := xcontext.AfterFuncSync(ctx, r.close) + defer func() { + if !detach() { + return + } + + r.close() + }() + + return r.srv.ActivateAndServe() +} + +func (r *Runner) close() { + l := r.logger + + if r.srv.Listener != nil { + l = l.With(zap.String("net", "tcp"), zap.String("local_addr", r.srv.Listener.Addr().String())) + } else if r.srv.PacketConn != nil { + l = l.With(zap.String("net", "udp"), zap.String("local_addr", r.srv.PacketConn.LocalAddr().String())) + } + + for { + err := r.srv.Shutdown() + if err != nil { + if strings.Contains(err.Error(), "server not started") { + // There a possible scenario where `go func()` not yet reached `ActivateAndServe` and yielded CPU + // time to another goroutine and then this closure reached `Shutdown`. In that case + // `dns.Server.ActivateAndServe` will actually start after `Shutdown` and this closure will block forever + // because `go func()` will never exit and close `done` channel. + continue + } + + l.Error("error shutting down dns server", zap.Error(err)) + } + + closer := io.Closer(r.srv.Listener) + if closer == nil { + closer = r.srv.PacketConn + } + + if closer != nil { + err = closer.Close() + if err != nil && !errors.Is(err, net.ErrClosed) { + l.Error("error closing dns server listener", zap.Error(err)) + } else { + l.Debug("dns server listener closed") + } + } + + break + } +} diff --git a/pkg/machinery/constants/constants.go b/pkg/machinery/constants/constants.go index dbb61e82c50..4957abcd973 100644 --- a/pkg/machinery/constants/constants.go +++ b/pkg/machinery/constants/constants.go @@ -1176,6 +1176,11 @@ const ( // Note: 116 = 't' and 108 = 'l' in ASCII. HostDNSAddress = "169.254.116.108" + // HostDNSAddressV6 is the address of the host DNS server in IPv6 network. + // + // Note: 5461:6c6f:7320:4f53:2044:4e53 is "Talos OS DNS". + HostDNSAddressV6 = "fe80:5461:6c6f:7320:4f53:2044:4e53:1" + // MetalAgentModeFlagPath is the path to the file indicating if the node is running in Metal Agent mode. MetalAgentModeFlagPath = "/usr/local/etc/is-metal-agent" ) diff --git a/pkg/machinery/resources/network/hostdns_config.go b/pkg/machinery/resources/network/hostdns_config.go index b59f4164015..41425622aa1 100644 --- a/pkg/machinery/resources/network/hostdns_config.go +++ b/pkg/machinery/resources/network/hostdns_config.go @@ -28,10 +28,11 @@ const HostDNSConfigID resource.ID = "config" // //gotagsrewrite:gen type HostDNSConfigSpec struct { - Enabled bool `yaml:"enabled" protobuf:"1"` - ListenAddresses []netip.AddrPort `yaml:"listenAddresses,omitempty" protobuf:"2"` - ServiceHostDNSAddress netip.Addr `yaml:"serviceHostDNSAddress,omitempty" protobuf:"3"` - ResolveMemberNames bool `yaml:"resolveMemberNames,omitempty" protobuf:"4"` + Enabled bool `yaml:"enabled" protobuf:"1"` + ListenAddresses []netip.AddrPort `yaml:"listenAddresses,omitempty" protobuf:"2"` + ServiceHostDNSAddress netip.Addr `yaml:"serviceHostDNSAddress,omitempty" protobuf:"3"` + ResolveMemberNames bool `yaml:"resolveMemberNames,omitempty" protobuf:"4"` + ServiceHostDNSAddressV6 netip.Addr `yaml:"serviceHostDNSAddressV6,omitempty" protobuf:"5"` } // NewHostDNSConfig initializes a HostDNSConfig resource.