Skip to content

Commit

Permalink
feat: support conditional start of IPv6 dns servers
Browse files Browse the repository at this point in the history
This PR does those things:
- Raise IPv6 listener on link-local address for dns (both TCP and UDP).
- Update kubelet's `resolv.conf` IPv4/IPv6 endpoints.

Closes siderolabs#9384

Signed-off-by: Dmitriy Matrenichev <[email protected]>
  • Loading branch information
DmitriyMV committed Nov 11, 2024
1 parent a07f66c commit ebd7bf6
Show file tree
Hide file tree
Showing 14 changed files with 831 additions and 649 deletions.
1 change: 1 addition & 0 deletions api/resource/definitions/network/network.proto
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ message HostDNSConfigSpec {
repeated common.NetIPPort listen_addresses = 2;
common.NetIP service_host_dns_address = 3;
bool resolve_member_names = 4;
common.NetIP service_host_dns_address_v6 = 5;
}

// HostnameSpecSpec describes node hostname.
Expand Down
42 changes: 42 additions & 0 deletions internal/app/machined/pkg/controllers/network/address_spec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"golang.org/x/sys/unix"

netctrl "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/network"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/nethelpers"
"github.com/siderolabs/talos/pkg/machinery/resources/network"
)
Expand Down Expand Up @@ -170,6 +171,47 @@ func (suite *AddressSpecSuite) TestLoopback() {
suite.Require().NoError(suite.state.Destroy(suite.ctx, loopback.Metadata()))
}

func (suite *AddressSpecSuite) TestIPV6ULA() {
loopback := network.NewAddressSpec(network.NamespaceName, "lo/"+constants.HostDNSAddressV6+"/128")
*loopback.TypedSpec() = network.AddressSpecSpec{
Address: netip.MustParsePrefix(constants.HostDNSAddressV6 + "/128"),
LinkName: "lo",
Family: nethelpers.FamilyInet6,
Scope: nethelpers.ScopeGlobal,
ConfigLayer: network.ConfigDefault,
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
}

for _, res := range []resource.Resource{loopback} {
suite.Require().NoError(suite.state.Create(suite.ctx, res), "%v", res.Spec())
}

suite.Assert().NoError(
retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
func() error {
return suite.assertLinkAddress("lo", constants.HostDNSAddressV6+"/128")
},
),
)

// teardown the address
for {
ready, err := suite.state.Teardown(suite.ctx, loopback.Metadata())
suite.Require().NoError(err)

if ready {
break
}

time.Sleep(100 * time.Millisecond)
}

// torn down address should be removed immediately
suite.Assert().NoError(suite.assertNoLinkAddress("lo", constants.HostDNSAddressV6+"/128"))

suite.Require().NoError(suite.state.Destroy(suite.ctx, loopback.Metadata()))
}

func (suite *AddressSpecSuite) TestDummy() {
dummyInterface := suite.uniqueDummyInterface()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func (ctrl *DNSResolveCacheController) run(ctx context.Context, r controller.Run
}

pairs := allAddressPairs(cfg.TypedSpec().ListenAddresses)
forwardKubeDNSToHost := cfg.TypedSpec().ServiceHostDNSAddress.IsValid()
forwardKubeDNSToHost := cfg.TypedSpec().ServiceHostDNSAddress.IsValid() || cfg.TypedSpec().ServiceHostDNSAddressV6.IsValid()

for runCfg, runErr := range ctrl.manager.RunAll(pairs, forwardKubeDNSToHost) {
switch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ func getDynamicPort() (string, error) {
func makeAddrs(port string) []netip.AddrPort {
return []netip.AddrPort{
netip.MustParseAddrPort("127.0.0.53:" + port),
netip.MustParseAddrPort("[::1]:" + port),
}
}

Expand Down
2 changes: 1 addition & 1 deletion internal/app/machined/pkg/controllers/network/etcfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func (ctrl *EtcFileController) Run(ctx context.Context, r controller.Runtime, _

if resolverStatus != nil && hostDNSCfg != nil {
dnsServers := xslices.FilterInPlace(
[]netip.Addr{hostDNSCfg.TypedSpec().ServiceHostDNSAddress},
[]netip.Addr{hostDNSCfg.TypedSpec().ServiceHostDNSAddress, hostDNSCfg.TypedSpec().ServiceHostDNSAddressV6},
netip.Addr.IsValid,
)

Expand Down
12 changes: 7 additions & 5 deletions internal/app/machined/pkg/controllers/network/etcfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,10 @@ func (suite *EtcFileConfigSuite) SetupTest() {
suite.hostDNSConfig.TypedSpec().ListenAddresses = []netip.AddrPort{
netip.MustParseAddrPort("127.0.0.53:53"),
netip.MustParseAddrPort("169.254.116.108:53"),
netip.MustParseAddrPort("[fd54:616c:6f73::204f:5320:444e:531]:53"),
}
suite.hostDNSConfig.TypedSpec().ServiceHostDNSAddress = netip.MustParseAddr("169.254.116.108")
suite.hostDNSConfig.TypedSpec().ServiceHostDNSAddressV6 = netip.MustParseAddr("fd54:616c:6f73::204f:5320:444e:531")
}

func (suite *EtcFileConfigSuite) startRuntime() {
Expand Down Expand Up @@ -228,7 +230,7 @@ func (suite *EtcFileConfigSuite) TestComplete() {
etcFileContents{
hosts: "127.0.0.1 localhost\n33.11.22.44 foo.example.com foo\n::1 localhost ip6-localhost ip6-loopback\nff02::1 ip6-allnodes\nff02::2 ip6-allrouters\n10.0.0.1 a b\n10.0.0.2 c d\n", //nolint:lll
resolvConf: "nameserver 127.0.0.53\n\nsearch example.com\n",
resolvGlobalConf: "nameserver 169.254.116.108\n\nsearch example.com\n",
resolvGlobalConf: "nameserver 169.254.116.108\nnameserver fd54:616c:6f73:0:204f:5320:444e:531\n\nsearch example.com\n",
},
)
}
Expand All @@ -239,7 +241,7 @@ func (suite *EtcFileConfigSuite) TestNoExtraHosts() {
etcFileContents{
hosts: "127.0.0.1 localhost\n33.11.22.44 foo.example.com foo\n::1 localhost ip6-localhost ip6-loopback\nff02::1 ip6-allnodes\nff02::2 ip6-allrouters\n",
resolvConf: "nameserver 127.0.0.53\n\nsearch example.com\n",
resolvGlobalConf: "nameserver 169.254.116.108\n\nsearch example.com\n",
resolvGlobalConf: "nameserver 169.254.116.108\nnameserver fd54:616c:6f73:0:204f:5320:444e:531\n\nsearch example.com\n",
},
)
}
Expand All @@ -262,7 +264,7 @@ func (suite *EtcFileConfigSuite) TestNoSearchDomain() {
etcFileContents{
hosts: "127.0.0.1 localhost\n33.11.22.44 foo.example.com foo\n::1 localhost ip6-localhost ip6-loopback\nff02::1 ip6-allnodes\nff02::2 ip6-allrouters\n",
resolvConf: "nameserver 127.0.0.53\n",
resolvGlobalConf: "nameserver 169.254.116.108\n",
resolvGlobalConf: "nameserver 169.254.116.108\nnameserver fd54:616c:6f73:0:204f:5320:444e:531\n",
},
)
}
Expand All @@ -275,7 +277,7 @@ func (suite *EtcFileConfigSuite) TestNoDomainname() {
etcFileContents{
hosts: "127.0.0.1 localhost\n33.11.22.44 foo\n::1 localhost ip6-localhost ip6-loopback\nff02::1 ip6-allnodes\nff02::2 ip6-allrouters\n",
resolvConf: "nameserver 127.0.0.53\n",
resolvGlobalConf: "nameserver 169.254.116.108\n",
resolvGlobalConf: "nameserver 169.254.116.108\nnameserver fd54:616c:6f73:0:204f:5320:444e:531\n",
},
)
}
Expand All @@ -286,7 +288,7 @@ func (suite *EtcFileConfigSuite) TestOnlyResolvers() {
etcFileContents{
hosts: "",
resolvConf: "nameserver 127.0.0.53\n",
resolvGlobalConf: "nameserver 169.254.116.108\n",
resolvGlobalConf: "nameserver 169.254.116.108\nnameserver fd54:616c:6f73:0:204f:5320:444e:531\n",
},
)
}
Expand Down
12 changes: 12 additions & 0 deletions internal/app/machined/pkg/controllers/network/hostdns_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ func (ctrl *HostDNSConfigController) Run(ctx context.Context, r controller.Runti
}

res.TypedSpec().ServiceHostDNSAddress = netip.Addr{}
res.TypedSpec().ServiceHostDNSAddressV6 = netip.Addr{}

if cfgProvider == nil {
res.TypedSpec().Enabled = false
Expand All @@ -117,6 +118,17 @@ func (ctrl *HostDNSConfigController) Run(ctx context.Context, r controller.Runti
res.TypedSpec().ServiceHostDNSAddress = parsed
}

if slices.ContainsFunc(
cfgProvider.Cluster().Network().PodCIDRs(),
func(cidr string) bool { return netip.MustParsePrefix(cidr).Addr().Is6() },
) {
parsed := netip.MustParseAddr(constants.HostDNSAddressV6)
newServiceAddrs = append(newServiceAddrs, parsed)

res.TypedSpec().ListenAddresses = append(res.TypedSpec().ListenAddresses, netip.AddrPortFrom(parsed, 53))
res.TypedSpec().ServiceHostDNSAddressV6 = parsed
}

return nil
}); err != nil {
return fmt.Errorf("error writing host dns config: %w", err)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/siderolabs/go-pointer"
"go.uber.org/zap"

cfg "github.com/siderolabs/talos/pkg/machinery/config/config"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/nethelpers"
"github.com/siderolabs/talos/pkg/machinery/resources/config"
Expand Down Expand Up @@ -154,8 +155,6 @@ func (ctrl *NfTablesChainConfigController) Run(ctx context.Context, r controller

if cfg.Config().Machine() != nil && cfg.Config().Cluster() != nil {
if cfg.Config().Machine().Features().HostDNS().ForwardKubeDNSToHost() {
hostDNSIP := netip.MustParseAddr(constants.HostDNSAddress)

// allow traffic to host DNS
for _, protocol := range []nethelpers.Protocol{nethelpers.ProtocolUDP, nethelpers.ProtocolTCP} {
spec.Rules = append(spec.Rules,
Expand All @@ -170,7 +169,7 @@ func (ctrl *NfTablesChainConfigController) Run(ctx context.Context, r controller
),
},
MatchDestinationAddress: &network.NfTablesAddressMatch{
IncludeSubnets: []netip.Prefix{netip.PrefixFrom(hostDNSIP, hostDNSIP.BitLen())},
IncludeSubnets: hostDNSSubnets(cfg.Config().Cluster().Network()),
},
MatchLayer4: &network.NfTablesLayer4Match{
Protocol: protocol,
Expand Down Expand Up @@ -256,3 +255,20 @@ func (ctrl *NfTablesChainConfigController) Run(ctx context.Context, r controller
}
}
}

func hostDNSSubnets(clusterNetwork cfg.ClusterNetwork) []netip.Prefix {
result := []netip.Addr{hostDNSIPv4}

for _, podCIDR := range clusterNetwork.PodCIDRs() {
if netip.MustParsePrefix(podCIDR).Addr().Is6() {
result = append(result, hostDNSIPv6)
}
}

return xslices.Map(result, func(a netip.Addr) netip.Prefix { return netip.PrefixFrom(a, a.BitLen()) })
}

var (
hostDNSIPv4 = netip.MustParseAddr(constants.HostDNSAddress)
hostDNSIPv6 = netip.MustParseAddr(constants.HostDNSAddressV6)
)
14 changes: 13 additions & 1 deletion internal/pkg/dns/dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func TestDNS(t *testing.T) {
},
}

for _, dnsAddr := range []string{"127.0.0.1:10700"} {
for _, dnsAddr := range []string{"127.0.0.1:10700", "[::1]:10700"} {
for _, test := range tests {
t.Run(dnsAddr+"/"+test.name, func(t *testing.T) {
stop := newManager(t, test.nameservers...)
Expand Down Expand Up @@ -115,6 +115,14 @@ func TestDNSEmptyDestinations(t *testing.T) {
require.NoError(t, err)
require.Equal(t, dnssrv.RcodeServerFailure, r.Rcode, r)

r, err = dnssrv.Exchange(createQuery("google.com"), "[::1]:10700")
require.NoError(t, err)
require.Equal(t, dnssrv.RcodeServerFailure, r.Rcode, r)

r, err = dnssrv.Exchange(createQuery("google.com"), "[::1]:10700")
require.NoError(t, err)
require.Equal(t, dnssrv.RcodeServerFailure, r.Rcode, r)

stop()
}

Expand All @@ -135,6 +143,8 @@ func TestGC_NOGC(t *testing.T) {
for _, err := range m.RunAll(slices.Values([]dns.AddressPair{
{Network: "udp", Addr: netip.MustParseAddrPort("127.0.0.1:10700")},
{Network: "udp", Addr: netip.MustParseAddrPort("127.0.0.1:10701")},
{Network: "udp", Addr: netip.MustParseAddrPort("[::1]:10700")},
{Network: "udp", Addr: netip.MustParseAddrPort("[::1]:10701")},
}), false) {
require.NoError(t, err)
}
Expand Down Expand Up @@ -195,6 +205,8 @@ func newManager(t *testing.T, nameservers ...string) func() {
for _, err := range m.RunAll(slices.Values([]dns.AddressPair{
{Network: "udp", Addr: netip.MustParseAddrPort("127.0.0.1:10700")},
{Network: "tcp", Addr: netip.MustParseAddrPort("127.0.0.1:10700")},
{Network: "udp", Addr: netip.MustParseAddrPort("[::1]:10700")},
{Network: "tcp", Addr: netip.MustParseAddrPort("[::1]:10700")},
}), false) {
if err != nil && strings.Contains(err.Error(), "failed to set TCP_FASTOPEN") {
continue
Expand Down
Loading

0 comments on commit ebd7bf6

Please sign in to comment.