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:
parent
4a3691a273
commit
ebeef28525
@ -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.
@ -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
19
go.mod
@ -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
41
go.sum
@ -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=
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -369,5 +369,7 @@ func NewKubeletConfiguration(cfgSpec *k8s.KubeletConfigSpec, kubeletVersion comp
|
||||
config.TLSMinVersion = "VersionTLS13"
|
||||
}
|
||||
|
||||
config.ResolverConfig = pointer.To(constants.PodResolvConfPath)
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}()
|
@ -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),
|
||||
}))
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -148,6 +148,7 @@ func NewState() (*State, error) {
|
||||
&network.AddressStatus{},
|
||||
&network.AddressSpec{},
|
||||
&network.DeviceConfigSpec{},
|
||||
&network.DNSResolveCache{},
|
||||
&network.HardwareAddr{},
|
||||
&network.HostnameStatus{},
|
||||
&network.HostnameSpec{},
|
||||
|
@ -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"}},
|
||||
|
27
internal/pkg/ctxutil/ctxutil.go
Normal file
27
internal/pkg/ctxutil/ctxutil.go
Normal 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
|
||||
}
|
33
internal/pkg/ctxutil/ctxutil_test.go
Normal file
33
internal/pkg/ctxutil/ctxutil_test.go
Normal 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
250
internal/pkg/dns/dns.go
Normal 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 }
|
221
internal/pkg/dns/dns_test.go
Normal file
221
internal/pkg/dns/dns_test.go
Normal 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
123
internal/pkg/utils/utils.go
Normal 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
|
||||
}
|
||||
}
|
118
internal/pkg/utils/utils_test.go
Normal file
118
internal/pkg/utils/utils_test.go
Normal 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
@ -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
|
||||
|
@ -414,6 +414,7 @@ type Features interface {
|
||||
KubernetesTalosAPIAccess() KubernetesTalosAPIAccess
|
||||
ApidCheckExtKeyUsageEnabled() bool
|
||||
DiskQuotaSupportEnabled() bool
|
||||
LocalDNSEnabled() bool
|
||||
KubePrism() KubePrism
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
},
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
65
pkg/machinery/resources/network/dns_resolve_cache.go
Normal file
65
pkg/machinery/resources/network/dns_resolve_cache.go
Normal 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)
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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. | |
|
||||
|
||||
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user