feat: implement local caching dns server

This PR adds a new controller - `DNSServerController` that starts tcp and udp dns servers locally. Just like `EtcFileController` it monitors `ResolverStatusType` and updates the list of destinations from there.

Most of the caching logic is in our "lobotomized" "`CoreDNS` fork. We need this fork because default `CoreDNS` carries
full Caddy server and various other modules that we don't need in Talos. On our side we implement
random selection of the actual dns and request forwarding.

Closes #7693

Signed-off-by: Dmitriy Matrenichev <dmitry.matrenichev@siderolabs.com>
This commit is contained in:
Dmitriy Matrenichev 2023-12-29 20:59:28 +03:00
parent 4a3691a273
commit ebeef28525
No known key found for this signature in database
GPG Key ID: D3363CF894E68892
44 changed files with 2757 additions and 1025 deletions

View File

@ -133,6 +133,7 @@ linters-settings:
- gopkg.in/yaml.v3
- github.com/vmware-tanzu/sonobuoy
- golang.org/x/sys
- github.com/coredns/coredns
retract-allow-no-explanation: false
exclude-forbidden: true

Binary file not shown.

View File

@ -90,6 +90,12 @@ message DHCP6OperatorSpec {
bool skip_hostname_request = 3;
}
// DNSResolveCacheSpec describes DNS servers status.
message DNSResolveCacheSpec {
string status = 1;
repeated common.NetIP servers = 2;
}
// HardwareAddrSpec describes spec for the link.
message HardwareAddrSpec {
string name = 1;

19
go.mod
View File

@ -3,6 +3,8 @@ module github.com/siderolabs/talos
go 1.21.6
replace (
// forked coredns so we don't carry caddy and other stuff into the Talos
github.com/coredns/coredns => github.com/siderolabs/coredns v1.11.51
// Use nested module.
github.com/siderolabs/talos/pkg/machinery => ./pkg/machinery
@ -43,6 +45,7 @@ require (
github.com/containerd/typeurl/v2 v2.1.1
github.com/containernetworking/cni v1.1.2
github.com/containernetworking/plugins v1.4.0
github.com/coredns/coredns v1.11.1
github.com/coreos/go-iptables v0.7.0
github.com/cosi-project/runtime v0.3.20
github.com/distribution/reference v0.5.0
@ -84,6 +87,7 @@ require (
github.com/mdlayher/netlink v1.7.2
github.com/mdlayher/netx v0.0.0-20230430222610-7e21880baee8
github.com/mdp/qrterminal/v3 v3.2.0
github.com/miekg/dns v1.1.57
github.com/nberlee/go-netstat v0.1.2
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0-rc4
@ -171,6 +175,7 @@ require (
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
github.com/ProtonMail/gopenpgp/v2 v2.7.4 // indirect
github.com/adrg/xdg v0.4.0 // indirect
github.com/apparentlymart/go-cidr v1.1.0 // indirect
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 // indirect
github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.16.15 // indirect
@ -199,7 +204,7 @@ require (
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/docker/cli v24.0.0+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
@ -208,7 +213,7 @@ require (
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v5.8.1+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gdamore/encoding v1.0.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect
@ -250,6 +255,7 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 // indirect
github.com/mdlayher/packet v1.1.2 // indirect
@ -272,6 +278,7 @@ require (
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d // indirect
github.com/opencontainers/selinux v1.11.0 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect
@ -306,10 +313,10 @@ require (
go.etcd.io/etcd/server/v3 v3.5.11 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect
go.opentelemetry.io/otel v1.20.0 // indirect
go.opentelemetry.io/otel/metric v1.20.0 // indirect
go.opentelemetry.io/otel/trace v1.20.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
go.opentelemetry.io/otel v1.21.0 // indirect
go.opentelemetry.io/otel/metric v1.21.0 // indirect
go.opentelemetry.io/otel/trace v1.21.0 // indirect
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.18.0 // indirect

41
go.sum
View File

@ -81,6 +81,8 @@ github.com/ProtonMail/gopenpgp/v2 v2.7.4 h1:Vz/8+HViFFnf2A6XX8JOvZMrA6F5puwNvvF2
github.com/ProtonMail/gopenpgp/v2 v2.7.4/go.mod h1:IhkNEDaxec6NyzSI0PlxapinnwPVIESk8/76da3Ct3g=
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU=
github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 h1:7Ip0wMmLHLRJdrloDxZfhMm0xrLXZS8+COSu2bXmEQs=
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU=
@ -174,6 +176,8 @@ github.com/containernetworking/cni v1.1.2 h1:wtRGZVv7olUHMOqouPpn3cXJWpJgM6+EUl3
github.com/containernetworking/cni v1.1.2/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw=
github.com/containernetworking/plugins v1.4.0 h1:+w22VPYgk7nQHw7KT92lsRmuToHvb7wwSv9iTbXzzic=
github.com/containernetworking/plugins v1.4.0/go.mod h1:UYhcOyjefnrQvKvmmyEKsUA+M9Nfn7tqULPpH0Pkcj0=
github.com/coredns/caddy v1.1.1 h1:2eYKZT7i6yxIfGP3qLJoJ7HAsDJqYB+X68g4NYjSrE0=
github.com/coredns/caddy v1.1.1/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
@ -189,8 +193,9 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
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=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
@ -232,8 +237,10 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZM
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
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-20240111164744-359f0856f017 h1:1tO4u/M0rLkGv58r93Dd9l0Fg6nuHWfuAWF6o7ZIdoM=
github.com/foxboron/go-uefi v0.0.0-20240111164744-359f0856f017/go.mod h1:VdozURTQHi5Rs54l+4Szi3yIJQDMfXXYrRLAjKKowWI=
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
@ -364,8 +371,8 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20230323073829-e72429f035bd h1:r8yyd+DJDmsUhGrRBxH5Pj7KeFK5l+Y3FsgT8keqKtk=
github.com/google/pprof v0.0.0-20230323073829-e72429f035bd/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo=
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
@ -480,6 +487,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
github.com/mdlayher/arp v0.0.0-20220512170110-6706a2966875 h1:ql8x//rJsHMjS+qqEag8n3i4azw1QneKh5PieH9UEbY=
@ -502,6 +511,8 @@ github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
github.com/mdp/qrterminal/v3 v3.2.0 h1:qteQMXO3oyTK4IHwj2mWsKYYRBOp1Pj2WRYFYYNTCdk=
github.com/mdp/qrterminal/v3 v3.2.0/go.mod h1:XGGuua4Lefrl7TLEsSONiD+UEjQXJZ4mPzF+gWYIJkk=
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@ -568,6 +579,8 @@ github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/
github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/packethost/packngo v0.31.0 h1:LLH90ardhULWbagBIc3I3nl2uU75io0a7AwY6hyi0S4=
github.com/packethost/packngo v0.31.0/go.mod h1:Io6VJqzkiqmIEQbpOjeIw9v8q9PfcTEq8TEY/tMQsfw=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
@ -633,6 +646,8 @@ github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sethgrid/pester v1.2.0 h1:adC9RS29rRUef3rIKWPOuP1Jm3/MmB6ke+OhE5giENI=
github.com/sethgrid/pester v1.2.0/go.mod h1:hEUINb4RqvDxtoCaU0BNT/HV4ig5kfgOasrf1xcvr0A=
github.com/siderolabs/coredns v1.11.51 h1:Re8IW3YW8uyHUsN2WKoHq8CIsUmUOqOK7WXINFfDOVE=
github.com/siderolabs/coredns v1.11.51/go.mod h1:DIdEvzamuQT2SvocW5hfdPI8hOK1jX8ZhyoWLfr0DcE=
github.com/siderolabs/crypto v0.4.1 h1:PP84WSDDyCCbjYKePcc0IaMSPXDndz8V3cQ9hMRSvpA=
github.com/siderolabs/crypto v0.4.1/go.mod h1:nJmvkqWy1Hngbzw3eg2TdtJ/ZYHHofQK1NbmmYywW8k=
github.com/siderolabs/discovery-api v0.1.3 h1:37ue+0w2A7Q2FrhyuDbfdhL4VPvDTpCzUYGvibhMwv0=
@ -780,14 +795,14 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 h1:PzIubN4/sjByhDRHLviCjJuweBXWFZWhghjg7cS28+M=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q=
go.opentelemetry.io/otel v1.20.0 h1:vsb/ggIY+hUjD/zCAQHpzTmndPqv/ml2ArbsbfBYTAc=
go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs=
go.opentelemetry.io/otel/metric v1.20.0 h1:ZlrO8Hu9+GAhnepmRGhSU7/VkpjrNowxRN9GyKR4wzA=
go.opentelemetry.io/otel/metric v1.20.0/go.mod h1:90DRw3nfK4D7Sm/75yQ00gTJxtkBxX+wu6YaNymbpVM=
go.opentelemetry.io/otel/trace v1.20.0 h1:+yxVAPZPbQhbC3OfAkeIVTky6iTFpcr4SiY9om7mXSQ=
go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY=
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=

View File

@ -37,6 +37,18 @@ Talos Linux now supports `physical: true` qualifier for device selectors, it sel
title = "Known Problems"
description = """\
ZFS and DRBD extensions are disabled in this release due to incompatibility with the latest Linux kernel.
"""
[notes.dns-resolve-cache]
title = "DNS Caching"
description = """\
Talos Linux now provides a caching DNS resolver for host workloads (including host networking pods). It can be disabled with:
```yaml
machine:
features:
localDNS: false
```
"""
[make_deps]

View File

@ -133,7 +133,7 @@ func (ctrl *EtcFileController) Run(ctx context.Context, r controller.Runtime, lo
logger.Debug("writing file contents", zap.String("dst", dst), zap.Stringer("version", spec.Metadata().Version()))
if err = updateFile(dst, spec.TypedSpec().Contents, spec.TypedSpec().Mode); err != nil {
if err = UpdateFile(dst, spec.TypedSpec().Contents, spec.TypedSpec().Mode); err != nil {
return fmt.Errorf("error updating %q: %w", dst, err)
}
@ -192,9 +192,9 @@ func createBindMount(src, dst string, mode os.FileMode) (err error) {
return nil
}
// updateFile is like `os.WriteFile`, but it will only update the file if the
// UpdateFile is like `os.WriteFile`, but it will only update the file if the
// contents have changed.
func updateFile(filename string, contents []byte, mode os.FileMode) error {
func UpdateFile(filename string, contents []byte, mode os.FileMode) error {
oldContents, err := os.ReadFile(filename)
if err == nil && bytes.Equal(oldContents, contents) {
return nil

View File

@ -16,6 +16,7 @@ import (
"github.com/cosi-project/runtime/pkg/controller/runtime"
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/safe"
"github.com/cosi-project/runtime/pkg/state"
"github.com/cosi-project/runtime/pkg/state/impl/inmem"
"github.com/cosi-project/runtime/pkg/state/impl/namespaced"
@ -87,10 +88,7 @@ func (suite *EtcFileSuite) assertEtcFile(filename, contents string, expectedVers
return retry.ExpectedErrorf("contents don't match %q != %q", string(b), contents)
}
r, err := suite.state.Get(
suite.ctx,
resource.NewMetadata(files.NamespaceName, files.EtcFileStatusType, filename, resource.VersionUndefined),
)
r, err := safe.ReaderGet[*files.EtcFileStatus](suite.ctx, suite.state, resource.NewMetadata(files.NamespaceName, files.EtcFileStatusType, filename, resource.VersionUndefined))
if err != nil {
if state.IsNotFoundError(err) {
return retry.ExpectedError(err)
@ -99,7 +97,7 @@ func (suite *EtcFileSuite) assertEtcFile(filename, contents string, expectedVers
return err
}
version := r.(*files.EtcFileStatus).TypedSpec().SpecVersion
version := r.TypedSpec().SpecVersion
expected, err := strconv.Atoi(expectedVersion.String())
suite.Require().NoError(err)

View File

@ -369,5 +369,7 @@ func NewKubeletConfiguration(cfgSpec *k8s.KubeletConfigSpec, kubeletVersion comp
config.TLSMinVersion = "VersionTLS13"
}
config.ResolverConfig = pointer.To(constants.PodResolvConfPath)
return config, nil
}

View File

@ -367,6 +367,7 @@ func TestNewKubeletConfigurationMerge(t *testing.T) {
TLSMinVersion: "VersionTLS13",
StaticPodPath: constants.ManifestsDirectory,
ContainerRuntimeEndpoint: "unix://" + constants.CRIContainerdAddress,
ResolverConfig: pointer.To(constants.PodResolvConfPath),
}
for _, tt := range []struct {

View File

@ -0,0 +1,220 @@
// 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 network
import (
"context"
"fmt"
"time"
"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"
"go.uber.org/zap"
"github.com/siderolabs/talos/internal/pkg/ctxutil"
"github.com/siderolabs/talos/internal/pkg/dns"
"github.com/siderolabs/talos/pkg/machinery/resources/config"
"github.com/siderolabs/talos/pkg/machinery/resources/network"
)
// DNSResolveCacheController starts dns server on both udp and tcp ports based on finalized network configuration.
type DNSResolveCacheController struct {
Addr string
Logger *zap.Logger
}
// Name implements controller.Controller interface.
func (ctrl *DNSResolveCacheController) Name() string {
return "network.DNSResolveCacheController"
}
// Inputs implements controller.Controller interface.
func (ctrl *DNSResolveCacheController) Inputs() []controller.Input {
return []controller.Input{
{
Namespace: network.NamespaceName,
Type: network.ResolverStatusType,
ID: optional.Some(network.ResolverID),
Kind: controller.InputWeak,
},
{
Namespace: config.NamespaceName,
Type: config.MachineConfigType,
ID: optional.Some(config.V1Alpha1ID),
Kind: controller.InputWeak,
},
}
}
// Outputs implements controller.Controller interface.
func (ctrl *DNSResolveCacheController) Outputs() []controller.Output {
return []controller.Output{
{
Type: network.DNSResolveCacheType,
Kind: controller.OutputExclusive,
},
}
}
// Run implements controller.Controller interface.
func (ctrl *DNSResolveCacheController) Run(ctx context.Context, r controller.Runtime, _ *zap.Logger) error {
for {
select {
case <-ctx.Done():
return nil
case <-r.EventCh():
}
mc, err := safe.ReaderGetByID[*config.MachineConfig](ctx, r, config.V1Alpha1ID)
if err != nil {
if state.IsNotFoundError(err) {
continue
}
return err
}
if !mc.Config().Machine().Features().LocalDNSEnabled() {
continue
}
err = func() error {
ctrl.Logger.Info("starting dns cache resolve")
defer ctrl.Logger.Info("stopping dns cache resolve")
return ctrl.runServer(ctx, r)
}()
if err != nil {
return err
}
}
}
func (ctrl *DNSResolveCacheController) writeDNSStatus(ctx context.Context, r controller.Runtime, net resource.ID, handler *dns.Handler) error {
return safe.WriterModify(ctx, r, network.NewDNSResolveCache(net), func(drc *network.DNSResolveCache) error {
drc.TypedSpec().Status = "running"
drc.TypedSpec().Servers = handler.ProxyList()
return nil
})
}
//nolint:gocyclo
func (ctrl *DNSResolveCacheController) runServer(originCtx context.Context, r controller.Runtime) error {
defer func() {
err := dropResolveResources(context.Background(), r, "tcp", "udp")
if err != nil {
ctrl.Logger.Error("error setting back the initial status", zap.Error(err))
}
}()
handler := dns.NewHandler(ctrl.Logger)
defer handler.Stop()
cache := dns.NewCache(handler, ctrl.Logger)
addr := ctrl.Addr
ctx := originCtx
for _, opt := range []dns.ServerOptins{
{
Addr: addr,
Net: "udp",
Handler: cache,
},
{
Addr: addr,
Net: "tcp",
Handler: cache,
ReadTimeout: 3 * time.Second,
WriteTimeout: 5 * time.Second,
IdleTimeout: func() time.Duration { return 10 * time.Second },
MaxTCPQueries: -1,
},
} {
l := ctrl.Logger.With(zap.String("net", opt.Net))
runner := dns.NewRunner(dns.NewServer(opt), l)
err := ctrl.writeDNSStatus(ctx, r, opt.Net, handler)
if err != nil {
return err
}
// We attach here our goroutine to the context, so if goroutine exits for some reason,
// context will be canceled too.
ctx = ctxutil.MonitorFn(ctx, runner.Run)
defer runner.Stop()
}
// Skip first iteration
eventCh := closedCh
for {
select {
case <-ctx.Done():
return ctxutil.Cause(ctx)
case <-eventCh:
}
eventCh = r.EventCh()
mc, err := safe.ReaderGetByID[*config.MachineConfig](ctx, r, config.V1Alpha1ID)
if err != nil {
return err
}
if !mc.Config().Machine().Features().LocalDNSEnabled() {
return nil
}
resolverStatus, err := safe.ReaderGetByID[*network.ResolverStatus](ctx, r, network.ResolverID)
if err != nil {
if state.IsNotFoundError(err) {
continue
}
return fmt.Errorf("error getting resolver status: %w", err)
}
ctrl.Logger.Info("updating dns server nameservers", zap.Stringers("data", resolverStatus.TypedSpec().DNSServers))
err = handler.SetProxy(resolverStatus.TypedSpec().DNSServers)
if err != nil {
return fmt.Errorf("error setting dns server nameservers: %w", err)
}
for _, n := range []string{"udp", "tcp"} {
err = ctrl.writeDNSStatus(ctx, r, n, handler)
if err != nil {
return err
}
}
}
}
func dropResolveResources(ctx context.Context, r controller.Runtime, nets ...resource.ID) error {
for _, net := range nets {
if err := r.Destroy(ctx, network.NewDNSResolveCache(net).Metadata()); err != nil {
if state.IsNotFoundError(err) {
continue
}
return fmt.Errorf("error destroying dns resolve cache resource: %w", err)
}
}
return nil
}
var closedCh = func() <-chan controller.ReconcileEvent {
res := make(chan controller.ReconcileEvent)
close(res)
return res
}()

View File

@ -0,0 +1,150 @@
// 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 network_test
import (
"net/netip"
"testing"
"time"
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/resource/rtestutils"
"github.com/miekg/dns"
"github.com/siderolabs/gen/xslices"
"github.com/siderolabs/go-pointer"
"github.com/siderolabs/go-retry/retry"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"go.uber.org/zap/zaptest"
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/ctest"
netctrl "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/network"
"github.com/siderolabs/talos/pkg/machinery/config/container"
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
"github.com/siderolabs/talos/pkg/machinery/resources/config"
"github.com/siderolabs/talos/pkg/machinery/resources/network"
)
type DNSServer struct {
ctest.DefaultSuite
}
func (suite *DNSServer) TestResolving() {
dnsSlice := []string{"8.8.8.8", "1.1.1.1"}
cfg := config.NewMachineConfig(
container.NewV1Alpha1(
&v1alpha1.Config{
ConfigVersion: "v1alpha1",
MachineConfig: &v1alpha1.MachineConfig{
MachineFeatures: &v1alpha1.FeaturesConfig{
LocalDNS: pointer.To(true),
},
},
},
),
)
suite.Require().NoError(suite.State().Create(suite.Ctx(), cfg))
resolverSpec := network.NewResolverStatus(network.NamespaceName, network.ResolverID)
resolverSpec.TypedSpec().DNSServers = xslices.Map(dnsSlice, netip.MustParseAddr)
suite.Require().NoError(suite.State().Create(suite.Ctx(), resolverSpec))
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{"tcp", "udp"}, func(r *network.DNSResolveCache, assert *assert.Assertions) {
assert.Equal("running", r.TypedSpec().Status)
assert.Equal(dnsSlice, xslices.Map(r.TypedSpec().Servers, netip.Addr.String))
})
msg := &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: dns.Id(),
RecursionDesired: true,
},
Question: []dns.Question{
{
Name: dns.Fqdn("google.com"),
Qtype: dns.TypeA,
Qclass: dns.ClassINET,
},
},
}
var res *dns.Msg
err := retry.Constant(2*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(func() error {
r, err := dns.Exchange(msg, "127.0.0.1:10700")
res = r
return retry.ExpectedError(err)
})
suite.Require().NoError(err)
suite.Require().Equal(dns.RcodeSuccess, res.Rcode, res)
}
func (suite *DNSServer) TestSetupStartStop() {
dnsSlice := []string{"8.8.8.8", "1.1.1.1"}
resolverSpec := network.NewResolverStatus(network.NamespaceName, network.ResolverID)
resolverSpec.TypedSpec().DNSServers = xslices.Map(dnsSlice, netip.MustParseAddr)
suite.Require().NoError(suite.State().Create(suite.Ctx(), resolverSpec))
cfg := config.NewMachineConfig(
container.NewV1Alpha1(
&v1alpha1.Config{
ConfigVersion: "v1alpha1",
MachineConfig: &v1alpha1.MachineConfig{
MachineFeatures: &v1alpha1.FeaturesConfig{
LocalDNS: pointer.To(true),
},
},
},
),
)
suite.Require().NoError(suite.State().Create(suite.Ctx(), cfg))
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{"tcp", "udp"}, func(r *network.DNSResolveCache, assert *assert.Assertions) {
assert.Equal("running", r.TypedSpec().Status)
assert.Equal(dnsSlice, xslices.Map(r.TypedSpec().Servers, netip.Addr.String))
})
// stop dns resolver
cfg.Container().RawV1Alpha1().MachineConfig.MachineFeatures.LocalDNS = pointer.To(false)
suite.Require().NoError(suite.State().Update(suite.Ctx(), cfg))
ctest.AssertNoResource[*network.DNSResolveCache](suite, "tcp")
ctest.AssertNoResource[*network.DNSResolveCache](suite, "udp")
// start dns resolver again
cfg.Container().RawV1Alpha1().MachineConfig.MachineFeatures.LocalDNS = pointer.To(true)
suite.Require().NoError(suite.State().Update(suite.Ctx(), cfg))
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{"tcp", "udp"}, func(r *network.DNSResolveCache, assert *assert.Assertions) {
assert.Equal("running", r.TypedSpec().Status)
assert.Equal(dnsSlice, xslices.Map(r.TypedSpec().Servers, netip.Addr.String))
})
}
func TestDNSServer(t *testing.T) {
suite.Run(t, &DNSServer{
DefaultSuite: ctest.DefaultSuite{
Timeout: 10 * time.Second,
AfterSetup: func(suite *ctest.DefaultSuite) {
suite.Require().NoError(suite.Runtime().RegisterController(&netctrl.DNSResolveCacheController{
Addr: ":10700",
Logger: zaptest.NewLogger(t),
}))
},
},
})
}

View File

@ -8,16 +8,19 @@ import (
"bytes"
"context"
"fmt"
"net/netip"
"os"
"path/filepath"
"strings"
"text/tabwriter"
"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"
"go.uber.org/zap"
efiles "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/files"
talosconfig "github.com/siderolabs/talos/pkg/machinery/config"
"github.com/siderolabs/talos/pkg/machinery/resources/config"
"github.com/siderolabs/talos/pkg/machinery/resources/files"
@ -25,7 +28,9 @@ import (
)
// EtcFileController creates /etc/hostname and /etc/resolv.conf files based on finalized network configuration.
type EtcFileController struct{}
type EtcFileController struct {
PodResolvConfPath string
}
// Name implements controller.Controller interface.
func (ctrl *EtcFileController) Name() string {
@ -53,6 +58,11 @@ func (ctrl *EtcFileController) Inputs() []controller.Input {
ID: optional.Some(network.ResolverID),
Kind: controller.InputWeak,
},
{
Namespace: network.NamespaceName,
Type: network.DNSResolveCacheType,
Kind: controller.InputWeak,
},
{
Namespace: network.NamespaceName,
Type: network.NodeAddressType,
@ -74,7 +84,7 @@ func (ctrl *EtcFileController) Outputs() []controller.Output {
// Run implements controller.Controller interface.
//
//nolint:gocyclo
//nolint:gocyclo,cyclop
func (ctrl *EtcFileController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
for {
select {
@ -94,44 +104,42 @@ func (ctrl *EtcFileController) Run(ctx context.Context, r controller.Runtime, lo
cfgProvider = cfg.Config()
}
var resolverStatus *network.ResolverStatusSpec
rStatus, err := r.Get(ctx, resource.NewMetadata(network.NamespaceName, network.ResolverStatusType, network.ResolverID, resource.VersionUndefined))
if err != nil {
if !state.IsNotFoundError(err) {
return fmt.Errorf("error getting resolver status: %w", err)
}
} else {
resolverStatus = rStatus.(*network.ResolverStatus).TypedSpec()
}
var hostnameStatus *network.HostnameStatusSpec
hStatus, err := r.Get(ctx, resource.NewMetadata(network.NamespaceName, network.HostnameStatusType, network.HostnameID, resource.VersionUndefined))
hostnameStatus, err := safe.ReaderGetByID[*network.HostnameStatus](ctx, r, network.HostnameID)
if err != nil {
if !state.IsNotFoundError(err) {
return fmt.Errorf("error getting hostname status: %w", err)
}
} else {
hostnameStatus = hStatus.(*network.HostnameStatus).TypedSpec()
}
var nodeAddressStatus *network.NodeAddressSpec
naStatus, err := r.Get(ctx, resource.NewMetadata(network.NamespaceName, network.NodeAddressType, network.NodeAddressDefaultID, resource.VersionUndefined))
nodeAddressStatus, err := safe.ReaderGetByID[*network.NodeAddress](ctx, r, network.NodeAddressDefaultID)
if err != nil {
if !state.IsNotFoundError(err) {
return fmt.Errorf("error getting network address status: %w", err)
}
} else {
nodeAddressStatus = naStatus.(*network.NodeAddress).TypedSpec()
}
if resolverStatus != nil {
if err = r.Modify(ctx, files.NewEtcFileSpec(files.NamespaceName, "resolv.conf"),
func(r resource.Resource) error {
r.(*files.EtcFileSpec).TypedSpec().Contents = ctrl.renderResolvConf(resolverStatus, hostnameStatus, cfgProvider)
r.(*files.EtcFileSpec).TypedSpec().Mode = 0o644
resolverStatus, err := safe.ReaderGetByID[*network.ResolverStatus](ctx, r, network.ResolverID)
if err != nil {
if !state.IsNotFoundError(err) {
return fmt.Errorf("error resolver status: %w", err)
}
}
dList, err := safe.ReaderListAll[*network.DNSResolveCache](ctx, r)
if err != nil {
return fmt.Errorf("error getting dns server list: %w", err)
}
var hostnameStatusSpec *network.HostnameStatusSpec
if hostnameStatus != nil {
hostnameStatusSpec = hostnameStatus.TypedSpec()
}
if dList.Len() > 0 || resolverStatus != nil {
if err = safe.WriterModify(ctx, r, files.NewEtcFileSpec(files.NamespaceName, "resolv.conf"),
func(r *files.EtcFileSpec) error {
r.TypedSpec().Contents = renderResolvConf(pickNameservers(dList, resolverStatus), hostnameStatusSpec, cfgProvider)
r.TypedSpec().Mode = 0o644
return nil
}); err != nil {
@ -139,15 +147,28 @@ func (ctrl *EtcFileController) Run(ctx context.Context, r controller.Runtime, lo
}
}
if resolverStatus != nil {
conf := renderResolvConf(resolverStatus.TypedSpec().DNSServers, hostnameStatusSpec, cfgProvider)
if err = os.MkdirAll(filepath.Dir(ctrl.PodResolvConfPath), 0o755); err != nil {
return fmt.Errorf("error creating pod resolv.conf dir: %w", err)
}
err = efiles.UpdateFile(ctrl.PodResolvConfPath, conf, 0o644)
if err != nil {
return fmt.Errorf("error writing pod resolv.conf: %w", err)
}
}
if hostnameStatus != nil && nodeAddressStatus != nil {
if err = r.Modify(ctx, files.NewEtcFileSpec(files.NamespaceName, "hosts"),
func(r resource.Resource) error {
r.(*files.EtcFileSpec).TypedSpec().Contents, err = ctrl.renderHosts(hostnameStatus, nodeAddressStatus, cfgProvider)
r.(*files.EtcFileSpec).TypedSpec().Mode = 0o644
if err = safe.WriterModify(ctx, r, files.NewEtcFileSpec(files.NamespaceName, "hosts"),
func(r *files.EtcFileSpec) error {
r.TypedSpec().Contents, err = ctrl.renderHosts(hostnameStatus.TypedSpec(), nodeAddressStatus.TypedSpec(), cfgProvider)
r.TypedSpec().Mode = 0o644
return err
}); err != nil {
return fmt.Errorf("error modifying resolv.conf: %w", err)
return fmt.Errorf("error modifying hosts: %w", err)
}
}
@ -155,16 +176,27 @@ func (ctrl *EtcFileController) Run(ctx context.Context, r controller.Runtime, lo
}
}
func (ctrl *EtcFileController) renderResolvConf(resolverStatus *network.ResolverStatusSpec, hostnameStatus *network.HostnameStatusSpec, cfgProvider talosconfig.Config) []byte {
var localDNS = netip.MustParseAddr("127.0.0.1")
func pickNameservers(list safe.List[*network.DNSResolveCache], resolverStatus *network.ResolverStatus) []netip.Addr {
if list.Len() > 0 {
// local dns resolve cache enabled, route host dns requests to 127.0.0.1
return []netip.Addr{localDNS}
}
return resolverStatus.TypedSpec().DNSServers
}
func renderResolvConf(nameservers []netip.Addr, hostnameStatus *network.HostnameStatusSpec, cfgProvider talosconfig.Config) []byte {
var buf bytes.Buffer
for i, resolver := range resolverStatus.DNSServers {
for i, ns := range nameservers {
if i >= 3 {
// only use firt 3 nameservers, see MAXNS in https://linux.die.net/man/5/resolv.conf
// only use first 3 nameservers, see MAXNS in https://linux.die.net/man/5/resolv.conf
break
}
fmt.Fprintf(&buf, "nameserver %s\n", resolver)
fmt.Fprintf(&buf, "nameserver %s\n", ns)
}
var disableSearchDomain bool

View File

@ -6,9 +6,12 @@ package network_test
import (
"context"
"errors"
"log"
"net/netip"
"net/url"
"os"
"path/filepath"
"sync"
"testing"
"time"
@ -19,6 +22,7 @@ import (
"github.com/cosi-project/runtime/pkg/state/impl/inmem"
"github.com/cosi-project/runtime/pkg/state/impl/namespaced"
"github.com/siderolabs/go-pointer"
"github.com/siderolabs/go-retry/retry"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
@ -46,6 +50,9 @@ type EtcFileConfigSuite struct {
defaultAddress *network.NodeAddress
hostnameStatus *network.HostnameStatus
resolverStatus *network.ResolverStatus
dnsServer *network.DNSResolveCache
podResolvConfPath string
}
func (suite *EtcFileConfigSuite) SetupTest() {
@ -60,7 +67,13 @@ func (suite *EtcFileConfigSuite) SetupTest() {
suite.startRuntime()
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.EtcFileController{}))
suite.podResolvConfPath = filepath.Join(suite.T().TempDir(), "resolv.conf")
suite.Assert().NoFileExists(suite.podResolvConfPath)
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.EtcFileController{
PodResolvConfPath: suite.podResolvConfPath,
}))
u, err := url.Parse("https://foo:6443")
suite.Require().NoError(err)
@ -108,6 +121,9 @@ func (suite *EtcFileConfigSuite) SetupTest() {
netip.MustParseAddr("3.3.3.3"),
netip.MustParseAddr("4.4.4.4"),
}
suite.dnsServer = network.NewDNSResolveCache("udp")
suite.dnsServer.TypedSpec().Status = "running"
}
func (suite *EtcFileConfigSuite) startRuntime() {
@ -120,65 +136,103 @@ func (suite *EtcFileConfigSuite) startRuntime() {
}()
}
func (suite *EtcFileConfigSuite) assertEtcFiles(requiredIDs []string, check func(*files.EtcFileSpec, *assert.Assertions)) {
assertResources(suite.ctx, suite.T(), suite.state, requiredIDs, check)
type etcFileContents struct {
hosts string
resolvConf string
resolvGlobalConf string
}
func (suite *EtcFileConfigSuite) assertNoEtcFile(id string) {
assertNoResource[*files.EtcFileSpec](suite.ctx, suite.T(), suite.state, id)
}
func (suite *EtcFileConfigSuite) testFiles(resources []resource.Resource, resolvConf, hosts string) {
//nolint:gocyclo
func (suite *EtcFileConfigSuite) testFiles(resources []resource.Resource, contents etcFileContents) {
for _, r := range resources {
suite.Require().NoError(suite.state.Create(suite.ctx, r))
}
expectedIds, unexpectedIds := []string{}, []string{}
var (
expectedIds []string
unexpectedIds []string
)
if resolvConf != "" {
if contents.resolvConf != "" {
expectedIds = append(expectedIds, "resolv.conf")
} else {
unexpectedIds = append(unexpectedIds, "resolv.conf")
}
if hosts != "" {
if contents.hosts != "" {
expectedIds = append(expectedIds, "hosts")
} else {
unexpectedIds = append(unexpectedIds, "hosts")
}
suite.assertEtcFiles(
assertResources(
suite.ctx,
suite.T(),
suite.state,
expectedIds,
func(r *files.EtcFileSpec, asrt *assert.Assertions) {
switch r.Metadata().ID() {
case "hosts":
asrt.Equal(hosts, string(r.TypedSpec().Contents))
asrt.Equal(contents.hosts, string(r.TypedSpec().Contents))
case "resolv.conf":
asrt.Equal(resolvConf, string(r.TypedSpec().Contents))
asrt.Equal(contents.resolvConf, string(r.TypedSpec().Contents))
}
},
)
suite.Assert().NoError(
retry.Constant(10*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(func() error {
if contents.resolvGlobalConf == "" {
_, err := os.Lstat(suite.podResolvConfPath)
switch {
case err == nil:
return retry.ExpectedErrorf("unexpected pod %s", suite.podResolvConfPath)
case errors.Is(err, os.ErrNotExist):
return nil
default:
return err
}
}
file, err := os.ReadFile(suite.podResolvConfPath)
switch {
case errors.Is(err, os.ErrNotExist):
return retry.ExpectedErrorf("missing pod %s", suite.podResolvConfPath)
case err != nil:
return err
default:
suite.Assert().Equal(contents.resolvGlobalConf, string(file))
return nil
}
}),
)
for _, id := range unexpectedIds {
id := id
suite.assertNoEtcFile(id)
assertNoResource[*files.EtcFileSpec](suite.ctx, suite.T(), suite.state, id)
}
}
func (suite *EtcFileConfigSuite) TestComplete() {
suite.testFiles(
[]resource.Resource{suite.cfg, suite.defaultAddress, suite.hostnameStatus, suite.resolverStatus},
"nameserver 1.1.1.1\nnameserver 2.2.2.2\nnameserver 3.3.3.3\n\nsearch example.com\n",
"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
[]resource.Resource{suite.cfg, suite.defaultAddress, suite.hostnameStatus, suite.resolverStatus, suite.dnsServer},
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.1\n\nsearch example.com\n",
resolvGlobalConf: "nameserver 1.1.1.1\nnameserver 2.2.2.2\nnameserver 3.3.3.3\n\nsearch example.com\n",
},
)
}
func (suite *EtcFileConfigSuite) TestNoExtraHosts() {
suite.testFiles(
[]resource.Resource{suite.defaultAddress, suite.hostnameStatus, suite.resolverStatus},
"nameserver 1.1.1.1\nnameserver 2.2.2.2\nnameserver 3.3.3.3\n\nsearch example.com\n",
"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",
[]resource.Resource{suite.defaultAddress, suite.hostnameStatus, suite.resolverStatus, suite.dnsServer},
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.1\n\nsearch example.com\n",
resolvGlobalConf: "nameserver 1.1.1.1\nnameserver 2.2.2.2\nnameserver 3.3.3.3\n\nsearch example.com\n",
},
)
}
@ -196,9 +250,12 @@ func (suite *EtcFileConfigSuite) TestNoSearchDomain() {
),
)
suite.testFiles(
[]resource.Resource{cfg, suite.defaultAddress, suite.hostnameStatus, suite.resolverStatus},
"nameserver 1.1.1.1\nnameserver 2.2.2.2\nnameserver 3.3.3.3\n",
"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",
[]resource.Resource{cfg, suite.defaultAddress, suite.hostnameStatus, suite.resolverStatus, suite.dnsServer},
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.1\n",
resolvGlobalConf: "nameserver 1.1.1.1\nnameserver 2.2.2.2\nnameserver 3.3.3.3\n",
},
)
}
@ -206,25 +263,34 @@ func (suite *EtcFileConfigSuite) TestNoDomainname() {
suite.hostnameStatus.TypedSpec().Domainname = ""
suite.testFiles(
[]resource.Resource{suite.defaultAddress, suite.hostnameStatus, suite.resolverStatus},
"nameserver 1.1.1.1\nnameserver 2.2.2.2\nnameserver 3.3.3.3\n",
"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",
[]resource.Resource{suite.defaultAddress, suite.hostnameStatus, suite.resolverStatus, suite.dnsServer},
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.1\n",
resolvGlobalConf: "nameserver 1.1.1.1\nnameserver 2.2.2.2\nnameserver 3.3.3.3\n",
},
)
}
func (suite *EtcFileConfigSuite) TestOnlyResolvers() {
suite.testFiles(
[]resource.Resource{suite.resolverStatus},
"nameserver 1.1.1.1\nnameserver 2.2.2.2\nnameserver 3.3.3.3\n",
"",
[]resource.Resource{suite.resolverStatus, suite.dnsServer},
etcFileContents{
hosts: "",
resolvConf: "nameserver 127.0.0.1\n",
resolvGlobalConf: "nameserver 1.1.1.1\nnameserver 2.2.2.2\nnameserver 3.3.3.3\n",
},
)
}
func (suite *EtcFileConfigSuite) TestOnlyHostname() {
suite.testFiles(
[]resource.Resource{suite.defaultAddress, suite.hostnameStatus},
"",
"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",
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: "",
resolvGlobalConf: "",
},
)
}
@ -233,6 +299,10 @@ func (suite *EtcFileConfigSuite) TearDownTest() {
suite.ctxCancel()
if _, err := os.Lstat(suite.podResolvConfPath); err == nil {
suite.Require().NoError(os.Remove(suite.podResolvConfPath))
}
suite.wg.Wait()
}

View File

@ -7,12 +7,10 @@ package network
import (
"context"
"fmt"
"net/netip"
"github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/state"
"github.com/siderolabs/gen/xslices"
"go.uber.org/zap"
"github.com/siderolabs/talos/pkg/machinery/resources/network"
@ -89,8 +87,7 @@ func (ctrl *ResolverSpecController) Run(ctx context.Context, r controller.Runtim
return fmt.Errorf("error removing finalizer: %w", err)
}
case resource.PhaseRunning:
resolvers := xslices.Map(spec.TypedSpec().DNSServers, netip.Addr.String)
logger.Info("setting resolvers", zap.Strings("resolvers", resolvers))
logger.Info("setting resolvers", zap.Stringers("resolvers", spec.TypedSpec().DNSServers))
if err = r.Modify(ctx, network.NewResolverStatus(network.NamespaceName, spec.Metadata().ID()), func(r resource.Resource) error {
status := r.(*network.ResolverStatus) //nolint:forcetypeassert,errcheck

View File

@ -87,11 +87,9 @@ func (suite *StatusSuite) TestHostname() {
}
func (suite *StatusSuite) TestEtcFiles() {
hosts := files.NewEtcFileStatus(files.NamespaceName, "hosts")
resolv := files.NewEtcFileStatus(files.NamespaceName, "resolv.conf")
suite.Require().NoError(suite.State().Create(suite.Ctx(), hosts))
suite.Require().NoError(suite.State().Create(suite.Ctx(), resolv))
for _, f := range []string{"hosts", "resolv.conf"} {
suite.Require().NoError(suite.State().Create(suite.Ctx(), files.NewEtcFileStatus(files.NamespaceName, f)))
}
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{network.StatusID}, func(r *network.Status, assert *assert.Assertions) {
assert.Equal(network.StatusSpec{EtcFilesReady: true}, *r.TypedSpec())

View File

@ -128,7 +128,10 @@ func (r *Runtime) CanApplyImmediate(cfg config.Provider) error {
// * .machine.pods
// * .machine.seccompProfiles
// * .machine.nodeLabels
// * .machine.nodeTaints
// * .machine.features.kubernetesTalosAPIAccess
// * .machine.features.kubePrism
// * .machine.features.localDNS
newConfig.ConfigDebug = currentConfig.ConfigDebug
newConfig.ClusterConfig = currentConfig.ClusterConfig
@ -149,14 +152,10 @@ func (r *Runtime) CanApplyImmediate(cfg config.Provider) error {
newConfig.MachineConfig.MachineNodeLabels = currentConfig.MachineConfig.MachineNodeLabels
newConfig.MachineConfig.MachineNodeTaints = currentConfig.MachineConfig.MachineNodeTaints
if newConfig.MachineConfig.MachineFeatures != nil {
if currentConfig.MachineConfig.MachineFeatures != nil {
newConfig.MachineConfig.MachineFeatures.KubernetesTalosAPIAccessConfig = currentConfig.MachineConfig.MachineFeatures.KubernetesTalosAPIAccessConfig
}
if currentConfig.MachineConfig.MachineFeatures != nil {
newConfig.MachineConfig.MachineFeatures.KubePrismSupport = currentConfig.MachineConfig.MachineFeatures.KubePrismSupport
}
if newConfig.MachineConfig.MachineFeatures != nil && currentConfig.MachineConfig.MachineFeatures != nil {
newConfig.MachineConfig.MachineFeatures.KubernetesTalosAPIAccessConfig = currentConfig.MachineConfig.MachineFeatures.KubernetesTalosAPIAccessConfig
newConfig.MachineConfig.MachineFeatures.KubePrismSupport = currentConfig.MachineConfig.MachineFeatures.KubePrismSupport
newConfig.MachineConfig.MachineFeatures.LocalDNS = currentConfig.MachineConfig.MachineFeatures.LocalDNS
}
}

View File

@ -64,16 +64,13 @@ func NewController(v1alpha1Runtime runtime.Runtime) (*Controller, error) {
v1alpha1Runtime: v1alpha1Runtime,
}
logWriter, err := ctrl.loggingManager.ServiceLog("controller-runtime").Writer()
var err error
ctrl.logger, err = ctrl.makeLogger("controller-runtime")
if err != nil {
return nil, err
}
ctrl.logger = logging.ZapLogger(
logging.NewLogDestination(logWriter, zapcore.DebugLevel, logging.WithColoredLevels()),
logging.NewLogDestination(logging.StdWriter, ctrl.consoleLogLevel, logging.WithoutTimestamp(), logging.WithoutLogLevels()),
).With(logging.Component("controller-runtime"))
ctrl.controllerRuntime, err = osruntime.NewRuntime(v1alpha1Runtime.State().V1Alpha2().Resources(), ctrl.logger)
return ctrl, err
@ -84,6 +81,11 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
// adjust the log level based on machine configuration
go ctrl.watchMachineConfig(ctx)
dnsCacheLogger, err := ctrl.makeLogger("dns-resolve-cache")
if err != nil {
return err
}
for _, c := range []controller.Controller{
&cluster.AffiliateMergeController{},
cluster.NewConfigController(),
@ -186,7 +188,13 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
&network.AddressSpecController{},
&network.AddressStatusController{},
&network.DeviceConfigController{},
&network.EtcFileController{},
&network.DNSResolveCacheController{
Addr: "127.0.0.1:53",
Logger: dnsCacheLogger,
},
&network.EtcFileController{
PodResolvConfPath: constants.PodResolvConfPath,
},
&network.HardwareAddrController{},
&network.HostnameConfigController{
Cmdline: procfs.ProcCmdline(),
@ -440,3 +448,15 @@ func (ctrl *Controller) updateLoggingConfig(ctx context.Context, dests []talosco
wg.Wait()
}
func (ctrl *Controller) makeLogger(s string) (*zap.Logger, error) {
logWriter, err := ctrl.loggingManager.ServiceLog(s).Writer()
if err != nil {
return nil, err
}
return logging.ZapLogger(
logging.NewLogDestination(logWriter, zapcore.DebugLevel, logging.WithColoredLevels()),
logging.NewLogDestination(logging.StdWriter, ctrl.consoleLogLevel, logging.WithoutTimestamp(), logging.WithoutLogLevels()),
).With(logging.Component(s)), nil
}

View File

@ -148,6 +148,7 @@ func NewState() (*State, error) {
&network.AddressStatus{},
&network.AddressSpec{},
&network.DeviceConfigSpec{},
&network.DNSResolveCache{},
&network.HardwareAddr{},
&network.HostnameStatus{},
&network.HostnameSpec{},

View File

@ -127,6 +127,7 @@ func (k *Kubelet) Runner(r runtime.Runtime) (runner.Runner, error) {
{Type: "bind", Destination: "/etc/nfsmount.conf", Source: "/etc/nfsmount.conf", Options: []string{"bind", "ro"}},
{Type: "bind", Destination: "/etc/machine-id", Source: "/etc/machine-id", Options: []string{"bind", "ro"}},
{Type: "bind", Destination: "/etc/os-release", Source: "/etc/os-release", Options: []string{"bind", "ro"}},
{Type: "bind", Destination: constants.PodResolvConfPath, Source: constants.PodResolvConfPath, Options: []string{"bind", "ro"}},
{Type: "bind", Destination: "/etc/cni", Source: "/etc/cni", Options: []string{"rbind", "rshared", "rw"}},
{Type: "bind", Destination: "/usr/libexec/kubernetes", Source: "/usr/libexec/kubernetes", Options: []string{"rbind", "rshared", "rw"}},
{Type: "bind", Destination: "/var/run", Source: "/run", Options: []string{"rbind", "rshared", "rw"}},

View File

@ -0,0 +1,27 @@
// 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 ctxutil provides utilities for working with contexts.
package ctxutil
import "context"
// MonitorFn starts a function in a new goroutine and cancels the context with error as cause when the function returns.
// It returns the new context.
func MonitorFn(ctx context.Context, fn func() error) context.Context {
ctx, cancel := context.WithCancelCause(ctx)
go func() { cancel(fn()) }()
return ctx
}
// Cause returns the cause of the context error, or nil if there is no error or the error is a usual context error.
func Cause(ctx context.Context) error {
if c := context.Cause(ctx); c != ctx.Err() {
return c
}
return nil
}

View File

@ -0,0 +1,33 @@
// 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 ctxutil_test
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/require"
"github.com/siderolabs/talos/internal/pkg/ctxutil"
)
func TestStartFn(t *testing.T) {
ctx := ctxutil.MonitorFn(context.Background(), func() error { return nil })
<-ctx.Done()
require.Equal(t, context.Canceled, ctx.Err())
require.Nil(t, ctxutil.Cause(ctx))
myErr := errors.New("my error")
ctx = ctxutil.MonitorFn(context.Background(), func() error { return myErr })
<-ctx.Done()
require.Equal(t, context.Canceled, ctx.Err())
require.Equal(t, myErr, ctxutil.Cause(ctx))
}

250
internal/pkg/dns/dns.go Normal file
View File

@ -0,0 +1,250 @@
// 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 provides dns server implementation.
package dns
import (
"context"
"errors"
"math/rand"
"net"
"net/netip"
"strings"
"sync"
"time"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/cache"
"github.com/coredns/coredns/plugin/pkg/proxy"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
"github.com/siderolabs/gen/pair"
"github.com/siderolabs/gen/xslices"
"go.uber.org/zap"
"github.com/siderolabs/talos/internal/pkg/utils"
)
// NewRunner creates a new Runner.
func NewRunner(srv Server, logger *zap.Logger) *Runner {
r := utils.NewRunner(srv.ListenAndServe, srv.Shutdown, func(err error) bool {
// There a possible scenario where `Run` reached `ListenAndServe` and then yielded CPU time to another
// goroutine and then `Stop` reached `Shutdown`. In that case `ListenAndServe` will actually start after
// `Shutdown` and `Stop` method will forever block if we do not try again.
return strings.Contains(err.Error(), "server not started")
})
return &Runner{r: r, logger: logger}
}
// Runner is a dns server handler.
type Runner struct {
r *utils.Runner
logger *zap.Logger
}
// Server is a dns server.
type Server interface {
ListenAndServe() error
Shutdown() error
}
// Run runs dns server.
func (r *Runner) Run() error {
r.logger.Info("starting dns server")
err := r.r.Run()
r.logger.Info("dns server stopped", zap.Error(err))
return err
}
// Stop stops dns server. It's safe to call even if server is already stopped.
func (r *Runner) Stop() {
err := r.r.Stop()
if err != nil {
r.logger.Warn("error shutting down dns server", zap.Error(err))
}
}
// Cache is a [dns.Handler] to [plugin.Handler] adapter.
type Cache struct {
cache *cache.Cache
logger *zap.Logger
}
// NewCache creates a new Cache.
func NewCache(next plugin.Handler, l *zap.Logger) *Cache {
c := cache.NewCache("zones", "view")
c.Next = next
return &Cache{cache: c, logger: l}
}
// ServeDNS implements [dns.Handler].
func (c *Cache) ServeDNS(wr dns.ResponseWriter, msg *dns.Msg) {
_, err := c.cache.ServeDNS(context.Background(), wr, msg)
if err != nil {
// we should probably call newProxy.Healthcheck() if there are too many errors
c.logger.Warn("error serving dns request", zap.Error(err))
}
}
// Handler is a dns proxy selector.
type Handler struct {
mx sync.RWMutex
dests []pair.Pair[netip.Addr, *proxy.Proxy]
logger *zap.Logger
}
// NewHandler creates a new Handler.
func NewHandler(logger *zap.Logger) *Handler {
return &Handler{
logger: logger,
}
}
// Name implements plugin.Handler.
func (h *Handler) Name() string {
return "Handler"
}
// ServeDNS implements plugin.Handler.
func (h *Handler) ServeDNS(ctx context.Context, wrt dns.ResponseWriter, msg *dns.Msg) (int, error) {
h.mx.RLock()
defer h.mx.RUnlock()
req := request.Request{W: wrt, Req: msg}
h.logger.Debug("dns request", zap.Stringer("data", msg))
upstreams := xslices.Map(h.dests, func(h pair.Pair[netip.Addr, *proxy.Proxy]) *proxy.Proxy { return h.F2 })
if len(upstreams) == 0 {
emptyProxyErr := new(dns.Msg).SetRcode(req.Req, dns.RcodeServerFailure)
err := wrt.WriteMsg(emptyProxyErr)
if err != nil {
// We can't do much here, but at least log the error.
h.logger.Warn("failed to write 'no destination available' error dns response", zap.Error(err))
}
return dns.RcodeServerFailure, errors.New("no destination available")
}
rand.Shuffle(len(upstreams), func(i, j int) { upstreams[i], upstreams[j] = upstreams[j], upstreams[i] })
var (
resp *dns.Msg
err error
)
for _, ups := range upstreams {
resp, err = ups.Connect(ctx, req, proxy.Options{})
if errors.Is(err, proxy.ErrCachedClosed) { // Remote side closed conn, can only happen with TCP.
continue
}
if err == nil {
break
}
continue
}
if err != nil {
return dns.RcodeServerFailure, err
}
if !req.Match(resp) {
resp = new(dns.Msg).SetRcode(req.Req, dns.RcodeFormatError)
err = wrt.WriteMsg(resp)
if err != nil {
// We can't do much here, but at least log the error.
h.logger.Warn("failed to write non-matched response", zap.Error(err))
}
h.logger.Warn("dns response didn't match", zap.Stringer("data", resp))
return 0, nil
}
err = wrt.WriteMsg(resp)
if err != nil {
// We can't do much here, but at least log the error.
h.logger.Warn("error writing dns response", zap.Error(err))
}
h.logger.Debug("dns response", zap.Stringer("data", resp))
return 0, nil
}
// SetProxy sets destination dns proxy servers.
func (h *Handler) SetProxy(servers []netip.Addr) error {
h.mx.Lock()
defer h.mx.Unlock()
var err error
h.dests, err = utils.UpdatePairSet(h.dests, servers, onAdd, onRemove)
return err
}
func onAdd(addr netip.Addr) (*proxy.Proxy, error) {
dst := addr.String()
result := proxy.NewProxy(dst, net.JoinHostPort(dst, "53"), "dns")
result.Start(500 * time.Millisecond)
return result, nil
}
func onRemove(h pair.Pair[netip.Addr, *proxy.Proxy]) error {
h.F2.Stop()
return nil
}
// Stop stops and clears dns proxy selector.
func (h *Handler) Stop() { h.SetProxy(nil) } //nolint:errcheck
// ProxyList returns a list of destination dns proxy servers.
func (h *Handler) ProxyList() []netip.Addr {
h.mx.RLock()
defer h.mx.RUnlock()
return xslices.Map(h.dests, func(h pair.Pair[netip.Addr, *proxy.Proxy]) netip.Addr { return h.F1 })
}
// ServerOptins is a Server options.
type ServerOptins struct {
Addr string
Net string
Handler dns.Handler
ReadTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout func() time.Duration
MaxTCPQueries int
}
// NewServer creates a new Server.
func NewServer(opts ServerOptins) Server {
return &server{&dns.Server{
Addr: opts.Addr,
Net: opts.Net,
Handler: opts.Handler,
ReadTimeout: opts.ReadTimeout,
WriteTimeout: opts.WriteTimeout,
IdleTimeout: opts.IdleTimeout,
MaxTCPQueries: opts.MaxTCPQueries,
}}
}
type server struct{ *dns.Server }

View File

@ -0,0 +1,221 @@
// 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_test
import (
"context"
"errors"
"net/netip"
"sync"
"sync/atomic"
"testing"
"time"
dnssrv "github.com/miekg/dns"
"github.com/siderolabs/gen/xslices"
"github.com/siderolabs/gen/xtesting/check"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zaptest"
"github.com/siderolabs/talos/internal/pkg/ctxutil"
"github.com/siderolabs/talos/internal/pkg/dns"
)
func TestDNS(t *testing.T) {
tests := []struct {
name string
nameservers []string
expectedCode int
errCheck check.Check
}{
{
name: "success",
nameservers: []string{"8.8.8.8"},
expectedCode: dnssrv.RcodeSuccess,
errCheck: check.NoError(),
},
{
name: "failure",
nameservers: []string{"242.242.242.242"},
errCheck: check.ErrorContains("i/o timeout"),
},
{
name: "empty destinations",
nameservers: nil,
expectedCode: dnssrv.RcodeServerFailure,
errCheck: check.NoError(),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx, stop := newServer(t, test.nameservers...)
stopOnce := sync.OnceFunc(stop)
defer stopOnce()
time.Sleep(10 * time.Millisecond)
r, err := dnssrv.Exchange(createQuery(), "127.0.0.1:10700")
test.errCheck(t, err)
if r != nil {
require.Equal(t, test.expectedCode, r.Rcode, r)
}
t.Logf("r: %s", r)
stopOnce()
<-ctx.Done()
require.NoError(t, ctxutil.Cause(ctx))
})
}
}
func TestDNSEmptyDestinations(t *testing.T) {
ctx, stop := newServer(t)
stopOnce := sync.OnceFunc(stop)
defer stopOnce()
time.Sleep(10 * time.Millisecond)
r, err := dnssrv.Exchange(createQuery(), "127.0.0.1:10700")
require.NoError(t, err)
require.Equal(t, dnssrv.RcodeServerFailure, r.Rcode, r)
r, err = dnssrv.Exchange(createQuery(), "127.0.0.1:10700")
require.NoError(t, err)
require.Equal(t, dnssrv.RcodeServerFailure, r.Rcode, r)
stopOnce()
<-ctx.Done()
require.NoError(t, ctxutil.Cause(ctx))
}
func newServer(t *testing.T, nameservers ...string) (context.Context, func()) {
l := zaptest.NewLogger(t)
handler := dns.NewHandler(l)
t.Cleanup(handler.Stop)
pxy := xslices.Map(nameservers, netip.MustParseAddr)
err := handler.SetProxy(pxy)
require.NoError(t, err)
require.Equal(t, pxy, handler.ProxyList())
runner := dns.NewRunner(dns.NewServer(dns.ServerOptins{
Addr: ":10700",
Net: "udp",
Handler: dns.NewCache(handler, l),
}), l)
return ctxutil.MonitorFn(context.Background(), runner.Run), runner.Stop
}
func createQuery() *dnssrv.Msg {
return &dnssrv.Msg{
MsgHdr: dnssrv.MsgHdr{
Id: dnssrv.Id(),
RecursionDesired: true,
},
Question: []dnssrv.Question{
{
Name: dnssrv.Fqdn("google.com"),
Qtype: dnssrv.TypeA,
Qclass: dnssrv.ClassINET,
},
},
}
}
func TestListenFailure(t *testing.T) {
// Ensure that we correctly handle an error inside [dns.Runner.Run].
l := zaptest.NewLogger(t)
runner := dns.NewRunner(&testServer{t: t}, l)
ctx := ctxutil.MonitorFn(context.Background(), runner.Run)
defer runner.Stop()
<-ctx.Done()
require.Equal(t, errFailed, ctxutil.Cause(ctx))
}
func TestRunnerStopsBeforeRun(t *testing.T) {
// Ensure that we correctly handle an error inside [dns.Runner.Run].
l := zap.NewNop()
for i := 0; i < 1000; i++ {
runner := dns.NewRunner(&runnerStopper{}, l)
ctx := ctxutil.MonitorFn(context.Background(), runner.Run)
runner.Stop()
<-ctx.Done()
}
for i := 0; i < 1000; i++ {
runner := dns.NewRunner(&runnerStopper{}, l)
runner.Stop()
ctx := ctxutil.MonitorFn(context.Background(), runner.Run)
<-ctx.Done()
}
}
type testServer struct {
t *testing.T
}
var errFailed = errors.New("listen failure")
func (ts *testServer) ListenAndServe() error { return errFailed }
func (ts *testServer) Shutdown() error {
ts.t.Fatal("should not be called")
return nil
}
func (ts *testServer) Name() string {
return "test-server"
}
type runnerStopper struct {
val atomic.Pointer[chan struct{}]
}
func (rs *runnerStopper) ListenAndServe() error {
ch := make(chan struct{})
if rs.val.Swap(&ch) != nil {
panic("chan should be empty")
}
<-ch
return nil
}
func (rs *runnerStopper) Shutdown() error {
chPtr := rs.val.Load()
if chPtr == nil {
return errors.New("server not started")
}
close(*chPtr)
return nil
}

123
internal/pkg/utils/utils.go Normal file
View File

@ -0,0 +1,123 @@
// 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 utils provides various utility functions.
package utils
import (
"errors"
"slices"
"sync/atomic"
"github.com/siderolabs/gen/pair"
)
// UpdatePairSet updates a set of pairs. It removes pairs that are not in toAdd and adds pairs that are not in old.
func UpdatePairSet[T comparable, H any](
old []pair.Pair[T, H],
toAdd []T,
add func(T) (H, error),
remove func(pair.Pair[T, H]) error,
) ([]pair.Pair[T, H], error) {
var err error
result := slices.DeleteFunc(old, func(h pair.Pair[T, H]) bool {
if err != nil {
return false
}
if slices.Contains(toAdd, h.F1) {
return false
}
err = remove(h)
if err != nil { //nolint:gosimple
return false
}
return true
})
if err != nil {
return result, err
}
for _, val := range toAdd {
if slices.ContainsFunc(old, func(h pair.Pair[T, H]) bool { return h.F1 == val }) {
continue
}
h, err := add(val)
if err != nil {
return result, err
}
result = append(result, pair.MakePair(val, h))
}
return result, nil
}
const (
notRunning = iota
running
closing
closed
)
// Runner is a fn/stop runner.
type Runner struct {
fn func() error
stop func() error
retryStop func(error) bool
status atomic.Int64
done chan struct{}
}
// NewRunner creates a new runner.
func NewRunner(fn, stop func() error, retryStop func(error) bool) *Runner {
return &Runner{fn: fn, stop: stop, retryStop: retryStop, done: make(chan struct{})}
}
// Run runs fn.
func (r *Runner) Run() error {
defer func() {
if r.status.Swap(closed) != closed {
close(r.done)
}
}()
if !r.status.CompareAndSwap(notRunning, running) {
return ErrAlreadyRunning
}
return r.fn()
}
var (
// ErrAlreadyRunning is the error that is returned when runner is already running/closing/closed.
ErrAlreadyRunning = errors.New("runner is already running/closing/closed")
// ErrNotRunning is the error that is returned when runner is not running/closing/closed.
ErrNotRunning = errors.New("runner is not running/closing/closed")
)
// Stop stops runner. It's safe to call even if runner is already stopped or in process of being stopped.
func (r *Runner) Stop() error {
if r.status.CompareAndSwap(notRunning, closing) || !r.status.CompareAndSwap(running, closing) {
return ErrNotRunning
}
for {
err := r.stop()
if err != nil {
if r.retryStop(err) && r.status.Load() == closing {
continue
}
}
<-r.done
return err
}
}

View File

@ -0,0 +1,118 @@
// 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 utils_test
import (
"errors"
"strconv"
"testing"
"github.com/siderolabs/gen/pair"
"github.com/siderolabs/gen/xslices"
"github.com/siderolabs/gen/xtesting/check"
"github.com/stretchr/testify/require"
"github.com/siderolabs/talos/internal/pkg/utils"
)
func TestHandleSet(t *testing.T) {
table := []struct {
state string
vals []int
expected []string
removed []string
expectedErr check.Check
}{
{
"initial state",
[]int{1, 2, 3},
[]string{"1", "2", "3"},
nil,
check.NoError(),
},
{
"add and remove",
[]int{1, 2, 4},
[]string{"1", "2", "4"},
[]string{"3"},
check.NoError(),
},
{
"add and remove with error",
[]int{1, 2, 5, 42, 43},
[]string{"1", "2", "5"},
[]string{"4"},
check.EqualError("42 is not allowed"),
},
{
"remove all",
[]int{},
nil,
[]string{"1", "2", "5"},
check.NoError(),
},
{
"start again",
[]int{1, 2, 3, 45, 46},
[]string{"1", "2", "3", "45", "46"},
nil,
check.NoError(),
},
{
"remove with error",
[]int{2, 3},
[]string{"2", "3", "45", "46"},
[]string{"1"},
check.EqualError("45 is not allowed to delete"),
},
{
"remove all again",
[]int{},
nil,
[]string{"2", "3", "45", "46"},
check.NoError(),
},
}
oneWithError := false
add := func(i int) (string, error) {
if i == 42 {
return "", errors.New("42 is not allowed")
}
return strconv.Itoa(i), nil
}
var removed []string
remove := func(h pair.Pair[int, string]) error {
if h.F1 == 45 && !oneWithError {
oneWithError = true
return errors.New("45 is not allowed to delete")
}
removed = append(removed, h.F2)
return nil
}
var hs []pair.Pair[int, string]
for _, tt := range table {
t.Run(tt.state, func(t *testing.T) {
removed = nil
var err error
hs, err = utils.UpdatePairSet(hs, tt.vals, add, remove)
tt.expectedErr(t, err)
require.Equal(t, tt.expected, xslices.Map(hs, func(h pair.Pair[int, string]) string { return h.F2 }))
require.Equal(t, tt.removed, removed)
})
}
}

File diff suppressed because it is too large Load Diff

View File

@ -699,6 +699,70 @@ func (m *DHCP6OperatorSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
return len(dAtA) - i, nil
}
func (m *DNSResolveCacheSpec) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *DNSResolveCacheSpec) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *DNSResolveCacheSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if len(m.Servers) > 0 {
for iNdEx := len(m.Servers) - 1; iNdEx >= 0; iNdEx-- {
if vtmsg, ok := interface{}(m.Servers[iNdEx]).(interface {
MarshalToSizedBufferVT([]byte) (int, error)
}); ok {
size, err := vtmsg.MarshalToSizedBufferVT(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarint(dAtA, i, uint64(size))
} else {
encoded, err := proto.Marshal(m.Servers[iNdEx])
if err != nil {
return 0, err
}
i -= len(encoded)
copy(dAtA[i:], encoded)
i = encodeVarint(dAtA, i, uint64(len(encoded)))
}
i--
dAtA[i] = 0x12
}
}
if len(m.Status) > 0 {
i -= len(m.Status)
copy(dAtA[i:], m.Status)
i = encodeVarint(dAtA, i, uint64(len(m.Status)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *HardwareAddrSpec) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
@ -3712,6 +3776,32 @@ func (m *DHCP6OperatorSpec) SizeVT() (n int) {
return n
}
func (m *DNSResolveCacheSpec) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Status)
if l > 0 {
n += 1 + l + sov(uint64(l))
}
if len(m.Servers) > 0 {
for _, e := range m.Servers {
if size, ok := interface{}(e).(interface {
SizeVT() int
}); ok {
l = size.SizeVT()
} else {
l = proto.Size(e)
}
n += 1 + l + sov(uint64(l))
}
}
n += len(m.unknownFields)
return n
}
func (m *HardwareAddrSpec) SizeVT() (n int) {
if m == nil {
return 0
@ -6357,6 +6447,131 @@ func (m *DHCP6OperatorSpec) UnmarshalVT(dAtA []byte) error {
}
return nil
}
func (m *DNSResolveCacheSpec) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: DNSResolveCacheSpec: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: DNSResolveCacheSpec: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Status = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Servers", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLength
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Servers = append(m.Servers, &common.NetIP{})
if unmarshal, ok := interface{}(m.Servers[len(m.Servers)-1]).(interface {
UnmarshalVT([]byte) error
}); ok {
if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
return err
}
} else {
if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.Servers[len(m.Servers)-1]); err != nil {
return err
}
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *HardwareAddrSpec) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0

View File

@ -414,6 +414,7 @@ type Features interface {
KubernetesTalosAPIAccess() KubernetesTalosAPIAccess
ApidCheckExtKeyUsageEnabled() bool
DiskQuotaSupportEnabled() bool
LocalDNSEnabled() bool
KubePrism() KubePrism
}

View File

@ -82,12 +82,12 @@ func TestSchemaValidation(t *testing.T) {
{
name: "network/RuleConfigV1Alpha1_invalid-cidr-prefix",
config: newRuleConfigV1Alpha1(t, nil, func(rawConfig map[string]any) {
rawConfig["ingress"] = []interface{}{
map[string]interface{}{
rawConfig["ingress"] = []any{
map[string]any{
"subnet": "10.42.0.0/16",
"except": "10.42.43.0/24",
},
map[string]interface{}{
map[string]any{
"subnet": "192.168.178.0/24",
"except": "invalid-except/12343",
},

View File

@ -179,3 +179,8 @@ func (contract *VersionContract) DiskQuotaSupportEnabled() bool {
func (contract *VersionContract) KubePrismEnabled() bool {
return contract.Greater(TalosVersion1_5)
}
// LocalDNSEnabled returns true if local dns router should be enabled by default.
func (contract *VersionContract) LocalDNSEnabled() bool {
return contract.Greater(TalosVersion1_6)
}

View File

@ -95,6 +95,10 @@ func (in *Input) init() ([]config.Document, error) {
machine.MachineKubelet.KubeletDisableManifestsDirectory = pointer.To(true)
}
if in.Options.VersionContract.LocalDNSEnabled() {
machine.MachineFeatures.LocalDNS = pointer.To(true)
}
certSANs := in.GetAPIServerSANs()
controlPlaneURL, err := url.Parse(in.ControlPlaneEndpoint)

View File

@ -1713,6 +1713,13 @@
"description": "KubePrism - local proxy/load balancer on defined port that will distribute\nrequests to all API servers in the cluster.\n",
"markdownDescription": "KubePrism - local proxy/load balancer on defined port that will distribute\nrequests to all API servers in the cluster.",
"x-intellij-html-description": "\u003cp\u003eKubePrism - local proxy/load balancer on defined port that will distribute\nrequests to all API servers in the cluster.\u003c/p\u003e\n"
},
"localDNS": {
"type": "boolean",
"title": "localDNS",
"description": "Enables local dns which routes all dns requests to the local caching router.\n",
"markdownDescription": "Enables local dns which routes all dns requests to the local caching router.",
"x-intellij-html-description": "\u003cp\u003eEnables local dns which routes all dns requests to the local caching router.\u003c/p\u003e\n"
}
},
"additionalProperties": false,
@ -1926,9 +1933,9 @@
"enabled": {
"type": "boolean",
"title": "enabled",
"description": "Enable KubePrism support - will start local load balacing proxy.\n",
"markdownDescription": "Enable KubePrism support - will start local load balacing proxy.",
"x-intellij-html-description": "\u003cp\u003eEnable KubePrism support - will start local load balacing proxy.\u003c/p\u003e\n"
"description": "Enable KubePrism support - will start local load balancing proxy.\n",
"markdownDescription": "Enable KubePrism support - will start local load balancing proxy.",
"x-intellij-html-description": "\u003cp\u003eEnable KubePrism support - will start local load balancing proxy.\u003c/p\u003e\n"
},
"port": {
"type": "integer",

View File

@ -39,6 +39,11 @@ func (f *FeaturesConfig) DiskQuotaSupportEnabled() bool {
return pointer.SafeDeref(f.DiskQuotaSupport)
}
// LocalDNSEnabled implements config.Features interface.
func (f *FeaturesConfig) LocalDNSEnabled() bool {
return pointer.SafeDeref(f.LocalDNS)
}
// KubePrism implements config.Features interface.
func (f *FeaturesConfig) KubePrism() config.KubePrism {
if f.KubePrismSupport == nil {

View File

@ -2151,12 +2151,15 @@ type FeaturesConfig struct {
// KubePrism - local proxy/load balancer on defined port that will distribute
// requests to all API servers in the cluster.
KubePrismSupport *KubePrism `yaml:"kubePrism,omitempty"`
// description: |
// Enables local dns which routes all dns requests to the local caching router.
LocalDNS *bool `yaml:"localDNS,omitempty"`
}
// KubePrism describes the configuration for the KubePrism load balancer.
type KubePrism struct {
// description: |
// Enable KubePrism support - will start local load balacing proxy.
// Enable KubePrism support - will start local load balancing proxy.
ServerEnabled *bool `yaml:"enabled,omitempty"`
// description: |
// KubePrism port.

View File

@ -3354,6 +3354,13 @@ func (FeaturesConfig) Doc() *encoder.Doc {
Description: "KubePrism - local proxy/load balancer on defined port that will distribute\nrequests to all API servers in the cluster.",
Comments: [3]string{"" /* encoder.HeadComment */, "KubePrism - local proxy/load balancer on defined port that will distribute" /* encoder.LineComment */, "" /* encoder.FootComment */},
},
{
Name: "localDNS",
Type: "bool",
Note: "",
Description: "Enables local dns which routes all dns requests to the local caching router.",
Comments: [3]string{"" /* encoder.HeadComment */, "Enables local dns which routes all dns requests to the local caching router." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
},
}
@ -3380,8 +3387,8 @@ func (KubePrism) Doc() *encoder.Doc {
Name: "enabled",
Type: "bool",
Note: "",
Description: "Enable KubePrism support - will start local load balacing proxy.",
Comments: [3]string{"" /* encoder.HeadComment */, "Enable KubePrism support - will start local load balacing proxy." /* encoder.LineComment */, "" /* encoder.FootComment */},
Description: "Enable KubePrism support - will start local load balancing proxy.",
Comments: [3]string{"" /* encoder.HeadComment */, "Enable KubePrism support - will start local load balancing proxy." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
{
Name: "port",

View File

@ -1058,6 +1058,11 @@ func (in *FeaturesConfig) DeepCopyInto(out *FeaturesConfig) {
*out = new(KubePrism)
(*in).DeepCopyInto(*out)
}
if in.LocalDNS != nil {
in, out := &in.LocalDNS, &out.LocalDNS
*out = new(bool)
**out = **in
}
return
}

View File

@ -968,6 +968,9 @@ const (
// DefaultNfTablesTableName is the default name of the nftables table created by Talos.
DefaultNfTablesTableName = "talos"
// PodResolvConfPath is the path to the pod resolv.conf file.
PodResolvConfPath = "/system/resolved/resolv.conf"
)
// See https://linux.die.net/man/3/klogctl

View File

@ -16,7 +16,7 @@ import (
"github.com/siderolabs/talos/pkg/machinery/proto"
)
//go:generate deep-copy -type AddressSpecSpec -type AddressStatusSpec -type HardwareAddrSpec -type HostnameSpecSpec -type HostnameStatusSpec -type LinkRefreshSpec -type LinkSpecSpec -type LinkStatusSpec -type NfTablesChainSpec -type NodeAddressSpec -type NodeAddressFilterSpec -type OperatorSpecSpec -type ProbeSpecSpec -type ProbeStatusSpec -type ResolverSpecSpec -type ResolverStatusSpec -type RouteSpecSpec -type RouteStatusSpec -type StatusSpec -type TimeServerSpecSpec -type TimeServerStatusSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go .
//go:generate deep-copy -type AddressSpecSpec -type AddressStatusSpec -type DNSResolveCacheSpec -type HardwareAddrSpec -type HostnameSpecSpec -type HostnameStatusSpec -type LinkRefreshSpec -type LinkSpecSpec -type LinkStatusSpec -type NfTablesChainSpec -type NodeAddressSpec -type NodeAddressFilterSpec -type OperatorSpecSpec -type ProbeSpecSpec -type ProbeStatusSpec -type ResolverSpecSpec -type ResolverStatusSpec -type RouteSpecSpec -type RouteStatusSpec -type StatusSpec -type TimeServerSpecSpec -type TimeServerStatusSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go .
// AddressSpecType is type of AddressSpec resource.
const AddressSpecType = resource.Type("AddressSpecs.net.talos.dev")

View File

@ -2,7 +2,7 @@
// 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/.
// Code generated by "deep-copy -type AddressSpecSpec -type AddressStatusSpec -type HardwareAddrSpec -type HostnameSpecSpec -type HostnameStatusSpec -type LinkRefreshSpec -type LinkSpecSpec -type LinkStatusSpec -type NfTablesChainSpec -type NodeAddressSpec -type NodeAddressFilterSpec -type OperatorSpecSpec -type ProbeSpecSpec -type ProbeStatusSpec -type ResolverSpecSpec -type ResolverStatusSpec -type RouteSpecSpec -type RouteStatusSpec -type StatusSpec -type TimeServerSpecSpec -type TimeServerStatusSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go ."; DO NOT EDIT.
// Code generated by "deep-copy -type AddressSpecSpec -type AddressStatusSpec -type DNSResolveCacheSpec -type HardwareAddrSpec -type HostnameSpecSpec -type HostnameStatusSpec -type LinkRefreshSpec -type LinkSpecSpec -type LinkStatusSpec -type NfTablesChainSpec -type NodeAddressSpec -type NodeAddressFilterSpec -type OperatorSpecSpec -type ProbeSpecSpec -type ProbeStatusSpec -type ResolverSpecSpec -type ResolverStatusSpec -type RouteSpecSpec -type RouteStatusSpec -type StatusSpec -type TimeServerSpecSpec -type TimeServerStatusSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go ."; DO NOT EDIT.
package network
@ -24,6 +24,16 @@ func (o AddressStatusSpec) DeepCopy() AddressStatusSpec {
return cp
}
// DeepCopy generates a deep copy of DNSResolveCacheSpec.
func (o DNSResolveCacheSpec) DeepCopy() DNSResolveCacheSpec {
var cp DNSResolveCacheSpec = o
if o.Servers != nil {
cp.Servers = make([]netip.Addr, len(o.Servers))
copy(cp.Servers, o.Servers)
}
return cp
}
// DeepCopy generates a deep copy of HardwareAddrSpec.
func (o HardwareAddrSpec) DeepCopy() HardwareAddrSpec {
var cp HardwareAddrSpec = o

View File

@ -0,0 +1,65 @@
// 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 network
import (
"net/netip"
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/resource/meta"
"github.com/cosi-project/runtime/pkg/resource/protobuf"
"github.com/cosi-project/runtime/pkg/resource/typed"
"github.com/siderolabs/talos/pkg/machinery/proto"
)
// DNSResolveCacheType is type of DNSResolveCache resource.
const DNSResolveCacheType = resource.Type("DNSResolveCaches.net.talos.dev")
// DNSResolveCache resource holds DNS resolver info.
type DNSResolveCache = typed.Resource[DNSResolveCacheSpec, DNSResolveCacheExtension]
// DNSResolveCacheSpec describes DNS servers status.
//
//gotagsrewrite:gen
type DNSResolveCacheSpec struct {
Status string `yaml:"status" protobuf:"1"`
Servers []netip.Addr `yaml:"servers" protobuf:"2"`
}
// NewDNSResolveCache initializes a DNSResolveCache resource.
func NewDNSResolveCache(id resource.ID) *DNSResolveCache {
return typed.NewResource[DNSResolveCacheSpec, DNSResolveCacheExtension](
resource.NewMetadata(NamespaceName, DNSResolveCacheType, id, resource.VersionUndefined),
DNSResolveCacheSpec{},
)
}
// DNSResolveCacheExtension provides auxiliary methods for DNSResolveCache.
type DNSResolveCacheExtension struct{}
// ResourceDefinition implements [typed.Extension] interface.
func (DNSResolveCacheExtension) ResourceDefinition() meta.ResourceDefinitionSpec {
return meta.ResourceDefinitionSpec{
Type: DNSResolveCacheType,
Aliases: []resource.Type{},
DefaultNamespace: NamespaceName,
PrintColumns: []meta.PrintColumn{
{
Name: "Status",
JSONPath: "{.status}",
},
},
}
}
func init() {
proto.RegisterDefaultTypes()
err := protobuf.RegisterDynamic[DNSResolveCacheSpec](DNSResolveCacheType, &DNSResolveCache{})
if err != nil {
panic(err)
}
}

View File

@ -158,6 +158,7 @@ description: Talos gRPC API reference.
- [BridgeSlave](#talos.resource.definitions.network.BridgeSlave)
- [DHCP4OperatorSpec](#talos.resource.definitions.network.DHCP4OperatorSpec)
- [DHCP6OperatorSpec](#talos.resource.definitions.network.DHCP6OperatorSpec)
- [DNSResolveCacheSpec](#talos.resource.definitions.network.DNSResolveCacheSpec)
- [HardwareAddrSpec](#talos.resource.definitions.network.HardwareAddrSpec)
- [HostnameSpecSpec](#talos.resource.definitions.network.HostnameSpecSpec)
- [HostnameStatusSpec](#talos.resource.definitions.network.HostnameStatusSpec)
@ -2898,6 +2899,22 @@ DHCP6OperatorSpec describes DHCP6 operator options.
<a name="talos.resource.definitions.network.DNSResolveCacheSpec"></a>
### DNSResolveCacheSpec
DNSResolveCacheSpec describes DNS servers status.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| status | [string](#string) | | |
| servers | [common.NetIP](#common.NetIP) | repeated | |
<a name="talos.resource.definitions.network.HardwareAddrSpec"></a>
### HardwareAddrSpec

View File

@ -2549,6 +2549,7 @@ kubernetesTalosAPIAccess:
|`apidCheckExtKeyUsage` |bool |Enable checks for extended key usage of client certificates in apid. | |
|`diskQuotaSupport` |bool |<details><summary>Enable XFS project quota support for EPHEMERAL partition and user disks.</summary>Also enables kubelet tracking of ephemeral disk usage in the kubelet via quota.</details> | |
|`kubePrism` |<a href="#Config.machine.features.kubePrism">KubePrism</a> |<details><summary>KubePrism - local proxy/load balancer on defined port that will distribute</summary>requests to all API servers in the cluster.</details> | |
|`localDNS` |bool |Enables local dns which routes all dns requests to the local caching router. | |
@ -2593,7 +2594,7 @@ KubePrism describes the configuration for the KubePrism load balancer.
| Field | Type | Description | Value(s) |
|-------|------|-------------|----------|
|`enabled` |bool |Enable KubePrism support - will start local load balacing proxy. | |
|`enabled` |bool |Enable KubePrism support - will start local load balancing proxy. | |
|`port` |int |KubePrism port. | |

View File

@ -1713,6 +1713,13 @@
"description": "KubePrism - local proxy/load balancer on defined port that will distribute\nrequests to all API servers in the cluster.\n",
"markdownDescription": "KubePrism - local proxy/load balancer on defined port that will distribute\nrequests to all API servers in the cluster.",
"x-intellij-html-description": "\u003cp\u003eKubePrism - local proxy/load balancer on defined port that will distribute\nrequests to all API servers in the cluster.\u003c/p\u003e\n"
},
"localDNS": {
"type": "boolean",
"title": "localDNS",
"description": "Enables local dns which routes all dns requests to the local caching router.\n",
"markdownDescription": "Enables local dns which routes all dns requests to the local caching router.",
"x-intellij-html-description": "\u003cp\u003eEnables local dns which routes all dns requests to the local caching router.\u003c/p\u003e\n"
}
},
"additionalProperties": false,
@ -1926,9 +1933,9 @@
"enabled": {
"type": "boolean",
"title": "enabled",
"description": "Enable KubePrism support - will start local load balacing proxy.\n",
"markdownDescription": "Enable KubePrism support - will start local load balacing proxy.",
"x-intellij-html-description": "\u003cp\u003eEnable KubePrism support - will start local load balacing proxy.\u003c/p\u003e\n"
"description": "Enable KubePrism support - will start local load balancing proxy.\n",
"markdownDescription": "Enable KubePrism support - will start local load balancing proxy.",
"x-intellij-html-description": "\u003cp\u003eEnable KubePrism support - will start local load balancing proxy.\u003c/p\u003e\n"
},
"port": {
"type": "integer",