Skip to content

Commit 908f67f

Browse files
author
Dmitriy Matrenichev
committed
feat: add host dns support for resolving member addrs
Closes #8330 Signed-off-by: Dmitriy Matrenichev <dmitry.matrenichev@siderolabs.com>
1 parent 0d20b63 commit 908f67f

12 files changed

Lines changed: 924 additions & 566 deletions

File tree

api/resource/definitions/network/network.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ message HostDNSConfigSpec {
106106
bool enabled = 1;
107107
repeated common.NetIPPort listen_addresses = 2;
108108
common.NetIP service_host_dns_address = 3;
109+
bool resolve_member_names = 4;
109110
}
110111

111112
// HostnameSpecSpec describes node hostname.

hack/release.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ machine:
5656
```
5757
5858
Please note that on running cluster you will have to kill CoreDNS pods for this change to apply.
59+
60+
If you want to can also enable the resolving of member addresses through their host and node names:
61+
62+
```yaml
63+
machine:
64+
features:
65+
hostDNS:
66+
enabled: true
67+
resolveMemberNames: true
68+
```
5969
"""
6070

6171
[notes.secureboot-image]

internal/app/machined/pkg/controllers/network/dns_resolve_cache.go

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,33 @@ import (
1010
"fmt"
1111
"net"
1212
"net/netip"
13+
"slices"
14+
"strings"
1315
"sync"
1416
"time"
1517

1618
"github.com/coredns/coredns/plugin/pkg/proxy"
1719
"github.com/cosi-project/runtime/pkg/controller"
1820
"github.com/cosi-project/runtime/pkg/safe"
1921
"github.com/cosi-project/runtime/pkg/state"
22+
dnssrv "github.com/miekg/dns"
2023
"github.com/siderolabs/gen/optional"
2124
"github.com/siderolabs/gen/pair"
2225
"go.uber.org/zap"
2326

2427
"github.com/siderolabs/talos/internal/pkg/dns"
28+
"github.com/siderolabs/talos/pkg/machinery/resources/cluster"
2529
"github.com/siderolabs/talos/pkg/machinery/resources/network"
2630
)
2731

2832
// DNSResolveCacheController starts dns server on both udp and tcp ports based on finalized network configuration.
2933
type DNSResolveCacheController struct {
34+
State state.State
3035
Logger *zap.Logger
3136

3237
mx sync.Mutex
3338
handler *dns.Handler
39+
nodeHandler *dns.NodeHandler
3440
cache *dns.Cache
3541
runners map[runnerConfig]pair.Pair[func(), <-chan struct{}]
3642
reconcile chan struct{}
@@ -115,6 +121,8 @@ func (ctrl *DNSResolveCacheController) Run(ctx context.Context, r controller.Run
115121
continue
116122
}
117123

124+
ctrl.nodeHandler.SetEnabled(cfg.TypedSpec().ResolveMemberNames)
125+
118126
touchedRunners := make(map[runnerConfig]struct{}, len(ctrl.runners))
119127

120128
for _, addr := range cfg.TypedSpec().ListenAddresses {
@@ -191,7 +199,8 @@ func (ctrl *DNSResolveCacheController) init(ctx context.Context) {
191199

192200
ctrl.originalCtx = ctx
193201
ctrl.handler = dns.NewHandler(ctrl.Logger)
194-
ctrl.cache = dns.NewCache(ctrl.handler, ctrl.Logger)
202+
ctrl.nodeHandler = dns.NewNodeHandler(ctrl.handler, &stateMapper{state: ctrl.State}, ctrl.Logger)
203+
ctrl.cache = dns.NewCache(ctrl.nodeHandler, ctrl.Logger)
195204
ctrl.runners = map[runnerConfig]pair.Pair[func(), <-chan struct{}]{}
196205
ctrl.reconcile = make(chan struct{}, 1)
197206

@@ -288,3 +297,49 @@ func newDNSRunner(cfg runnerConfig, cache *dns.Cache, logger *zap.Logger) (*dns.
288297

289298
return dns.NewServer(serverOpts), nil
290299
}
300+
301+
type stateMapper struct {
302+
state state.State
303+
}
304+
305+
func (s *stateMapper) ResolveAddr(ctx context.Context, qType uint16, name string) []netip.Addr {
306+
name = strings.TrimRight(name, ".")
307+
308+
list, err := safe.ReaderListAll[*cluster.Member](ctx, s.state)
309+
if err != nil {
310+
return nil
311+
}
312+
313+
elem, ok := list.Find(func(res *cluster.Member) bool {
314+
return fqdnMatch(name, res.TypedSpec().Hostname) || fqdnMatch(name, res.Metadata().ID())
315+
})
316+
if !ok {
317+
return nil
318+
}
319+
320+
result := slices.DeleteFunc(slices.Clone(elem.TypedSpec().Addresses), func(addr netip.Addr) bool {
321+
return !((qType == dnssrv.TypeA && addr.Is4()) || (qType == dnssrv.TypeAAAA && addr.Is6()))
322+
})
323+
324+
if len(result) == 0 {
325+
return nil
326+
}
327+
328+
return result
329+
}
330+
331+
func fqdnMatch(what, where string) bool {
332+
what = strings.TrimRight(what, ".")
333+
where = strings.TrimRight(where, ".")
334+
335+
if what == where {
336+
return true
337+
}
338+
339+
first, _, found := strings.Cut(where, ".")
340+
if !found {
341+
return false
342+
}
343+
344+
return what == first
345+
}

internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package network_test
66

77
import (
8+
"errors"
89
"net"
910
"net/netip"
1011
"sync"
@@ -23,6 +24,8 @@ import (
2324

2425
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/ctest"
2526
netctrl "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/network"
27+
"github.com/siderolabs/talos/pkg/machinery/config/machine"
28+
"github.com/siderolabs/talos/pkg/machinery/resources/cluster"
2629
"github.com/siderolabs/talos/pkg/machinery/resources/network"
2730
)
2831

@@ -141,6 +144,108 @@ func (suite *DNSServer) TestSetupStartStop() {
141144
rtestutils.AssertLength[*network.DNSUpstream](suite.Ctx(), suite.T(), suite.State(), len(dnsSlice))
142145
}
143146

147+
func (suite *DNSServer) TestResolveMembers() {
148+
port := must.Value(getDynamicPort())(suite.T())
149+
150+
const (
151+
id = "talos-default-controlplane-1"
152+
id2 = "foo.example.com."
153+
)
154+
155+
member := cluster.NewMember(cluster.NamespaceName, id)
156+
*member.TypedSpec() = cluster.MemberSpec{
157+
NodeID: id,
158+
Addresses: []netip.Addr{
159+
netip.MustParseAddr("172.20.0.2"),
160+
},
161+
Hostname: id,
162+
MachineType: machine.TypeControlPlane,
163+
OperatingSystem: "Talos dev",
164+
ControlPlane: nil,
165+
}
166+
167+
suite.Require().NoError(suite.State().Create(suite.Ctx(), member))
168+
169+
member = cluster.NewMember(cluster.NamespaceName, id2)
170+
*member.TypedSpec() = cluster.MemberSpec{
171+
NodeID: id2,
172+
Addresses: []netip.Addr{
173+
netip.MustParseAddr("172.20.0.3"),
174+
},
175+
Hostname: id2,
176+
MachineType: machine.TypeWorker,
177+
OperatingSystem: "Talos dev",
178+
ControlPlane: nil,
179+
}
180+
181+
suite.Require().NoError(suite.State().Create(suite.Ctx(), member))
182+
183+
cfg := network.NewHostDNSConfig(network.HostDNSConfigID)
184+
cfg.TypedSpec().Enabled = true
185+
cfg.TypedSpec().ListenAddresses = makeAddrs(port)
186+
cfg.TypedSpec().ResolveMemberNames = true
187+
suite.Require().NoError(suite.State().Create(suite.Ctx(), cfg))
188+
189+
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(),
190+
expectedDNSRunners(port),
191+
func(r *network.DNSResolveCache, assert *assert.Assertions) {
192+
assert.Equal("running", r.TypedSpec().Status)
193+
},
194+
)
195+
196+
suite.Require().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(func() error {
197+
exchange, err := dns.Exchange(
198+
&dns.Msg{
199+
MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: true},
200+
Question: []dns.Question{
201+
{Name: dns.Fqdn(id), Qtype: dns.TypeA, Qclass: dns.ClassINET},
202+
},
203+
},
204+
"127.0.0.53:"+port,
205+
)
206+
if err != nil {
207+
return retry.ExpectedError(err)
208+
}
209+
210+
if exchange.Rcode != dns.RcodeSuccess {
211+
return retry.ExpectedErrorf("expected rcode %d, got %d for %q", dns.RcodeSuccess, exchange.Rcode, id)
212+
}
213+
214+
proper := dns.Fqdn(id)
215+
216+
if exchange.Answer[0].Header().Name != proper {
217+
return retry.ExpectedErrorf("expected answer name %q, got %q", proper, exchange.Answer[0].Header().Name)
218+
}
219+
220+
return nil
221+
}))
222+
223+
suite.Require().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(func() error {
224+
exchange, err := dns.Exchange(
225+
&dns.Msg{
226+
MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: true},
227+
Question: []dns.Question{
228+
{Name: dns.Fqdn("foo"), Qtype: dns.TypeA, Qclass: dns.ClassINET},
229+
},
230+
},
231+
"127.0.0.53:"+port,
232+
)
233+
if err != nil {
234+
return retry.ExpectedError(err)
235+
}
236+
237+
if exchange.Rcode != dns.RcodeSuccess {
238+
return retry.ExpectedErrorf("expected rcode %d, got %d for %q", dns.RcodeSuccess, exchange.Rcode, id2)
239+
}
240+
241+
if !exchange.Answer[0].(*dns.A).A.Equal(net.ParseIP("172.20.0.3")) {
242+
return retry.ExpectedError(errors.New("unexpected ip"))
243+
}
244+
245+
return nil
246+
}))
247+
}
248+
144249
func TestDNSServer(t *testing.T) {
145250
suite.Run(t, &DNSServer{
146251
DefaultSuite: ctest.DefaultSuite{
@@ -149,6 +254,7 @@ func TestDNSServer(t *testing.T) {
149254
suite.Require().NoError(suite.Runtime().RegisterController(&netctrl.DNSUpstreamController{}))
150255
suite.Require().NoError(suite.Runtime().RegisterController(&netctrl.DNSResolveCacheController{
151256
Logger: zaptest.NewLogger(t),
257+
State: suite.State(),
152258
}))
153259
},
154260
},

internal/app/machined/pkg/controllers/network/hostdns_config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ func (ctrl *HostDNSConfigController) Run(ctx context.Context, r controller.Runti
9898
}
9999

100100
res.TypedSpec().Enabled = cfgProvider.Machine().Features().HostDNS().Enabled()
101+
res.TypedSpec().ResolveMemberNames = cfgProvider.Machine().Features().HostDNS().ResolveMemberNames()
101102

102103
if cfgProvider.Machine().Features().HostDNS().ForwardKubeDNSToHost() {
103104
serviceCIDRStr := cfgProvider.Cluster().Network().ServiceCIDRs()[0]

internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
194194
&network.AddressStatusController{},
195195
&network.DeviceConfigController{},
196196
&network.DNSResolveCacheController{
197+
State: ctrl.v1alpha1Runtime.State().V1Alpha2().Resources(),
197198
Logger: dnsCacheLogger,
198199
},
199200
&network.DNSUpstreamController{},

0 commit comments

Comments
 (0)