mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-05 17:16:59 +02:00
Introduced NAT64 prefix rewriting
This commit is contained in:
parent
8245b89891
commit
b8e018caaf
1
main.go
1
main.go
@ -179,6 +179,7 @@ func main() {
|
||||
|
||||
// Combine multiple sources into a single, deduplicated source.
|
||||
endpointsSource := source.NewDedupSource(source.NewMultiSource(sources, sourceCfg.DefaultTargets))
|
||||
endpointsSource = source.NewNAT64Source(endpointsSource, cfg.NAT64Networks)
|
||||
endpointsSource = source.NewTargetFilterSource(endpointsSource, targetFilter)
|
||||
|
||||
// RegexDomainFilter overrides DomainFilter
|
||||
|
@ -213,6 +213,7 @@ type Config struct {
|
||||
WebhookServer bool
|
||||
TraefikDisableLegacy bool
|
||||
TraefikDisableNew bool
|
||||
NAT64Networks []string
|
||||
}
|
||||
|
||||
var defaultConfig = &Config{
|
||||
@ -363,6 +364,7 @@ var defaultConfig = &Config{
|
||||
WebhookServer: false,
|
||||
TraefikDisableLegacy: false,
|
||||
TraefikDisableNew: false,
|
||||
NAT64Networks: []string{},
|
||||
}
|
||||
|
||||
// NewConfig returns new Config object
|
||||
@ -452,6 +454,7 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
app.Flag("exclude-target-net", "Exclude target nets (optional)").StringsVar(&cfg.ExcludeTargetNets)
|
||||
app.Flag("traefik-disable-legacy", "Disable listeners on Resources under the traefik.containo.us API Group").Default(strconv.FormatBool(defaultConfig.TraefikDisableLegacy)).BoolVar(&cfg.TraefikDisableLegacy)
|
||||
app.Flag("traefik-disable-new", "Disable listeners on Resources under the traefik.io API Group").Default(strconv.FormatBool(defaultConfig.TraefikDisableNew)).BoolVar(&cfg.TraefikDisableNew)
|
||||
app.Flag("nat64-networks", "Adding an A record for each AAAA record in NAT64-enabled networks; specify multiple times for multiple possible nets (optional)").StringsVar(&cfg.NAT64Networks)
|
||||
|
||||
// Flags related to providers
|
||||
providers := []string{"akamai", "alibabacloud", "aws", "aws-sd", "azure", "azure-dns", "azure-private-dns", "bluecat", "civo", "cloudflare", "coredns", "designate", "digitalocean", "dnsimple", "dyn", "exoscale", "gandi", "godaddy", "google", "ibmcloud", "inmemory", "linode", "ns1", "oci", "ovh", "pdns", "pihole", "plural", "rcodezero", "rdns", "rfc2136", "safedns", "scaleway", "skydns", "tencentcloud", "transip", "ultradns", "vinyldns", "vultr", "webhook"}
|
||||
|
112
source/nat64source.go
Normal file
112
source/nat64source.go
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package source
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
)
|
||||
|
||||
// nat64Source is a Source that adds A endpoints for AAAA records including an NAT64 address.
|
||||
type nat64Source struct {
|
||||
source Source
|
||||
nat64Prefixes []string
|
||||
}
|
||||
|
||||
// NewNAT64Source creates a new nat64Source wrapping the provided Source.
|
||||
func NewNAT64Source(source Source, nat64Prefixes []string) Source {
|
||||
return &nat64Source{source: source, nat64Prefixes: nat64Prefixes}
|
||||
}
|
||||
|
||||
// Endpoints collects endpoints from its wrapped source and returns them without duplicates.
|
||||
func (s *nat64Source) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
||||
parsedNAT64Prefixes := make([]netip.Prefix, 0)
|
||||
for _, prefix := range s.nat64Prefixes {
|
||||
pPrefix, err := netip.ParsePrefix(prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if pPrefix.Bits() != 96 {
|
||||
return nil, fmt.Errorf("NAT64 prefixes need to be /96 prefixes.")
|
||||
}
|
||||
parsedNAT64Prefixes = append(parsedNAT64Prefixes, pPrefix)
|
||||
}
|
||||
|
||||
additionalEndpoints := []*endpoint.Endpoint{}
|
||||
|
||||
endpoints, err := s.source.Endpoints(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, ep := range endpoints {
|
||||
if ep.RecordType != endpoint.RecordTypeAAAA {
|
||||
continue
|
||||
}
|
||||
|
||||
v4Targets := make([]string, 0)
|
||||
|
||||
for _, target := range ep.Targets {
|
||||
ip, err := netip.ParseAddr(target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var sPrefix *netip.Prefix
|
||||
|
||||
for _, cPrefix := range parsedNAT64Prefixes {
|
||||
if cPrefix.Contains(ip) {
|
||||
sPrefix = &cPrefix
|
||||
}
|
||||
}
|
||||
|
||||
// If we do not have a NAT64 prefix, we skip this record.
|
||||
if sPrefix == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ipBytes := ip.As16()
|
||||
v4AddrBytes := ipBytes[12:16]
|
||||
|
||||
v4Addr, isOk := netip.AddrFromSlice(v4AddrBytes)
|
||||
if !isOk {
|
||||
return nil, fmt.Errorf("Could not parse %v to IPv4 address", v4AddrBytes)
|
||||
}
|
||||
|
||||
v4Targets = append(v4Targets, v4Addr.String())
|
||||
}
|
||||
|
||||
if len(v4Targets) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
v4EP := ep.DeepCopy()
|
||||
v4EP.Targets = v4Targets
|
||||
v4EP.RecordType = endpoint.RecordTypeA
|
||||
|
||||
additionalEndpoints = append(additionalEndpoints, v4EP)
|
||||
}
|
||||
return append(endpoints, additionalEndpoints...), nil
|
||||
}
|
||||
|
||||
func (s *nat64Source) AddEventHandler(ctx context.Context, handler func()) {
|
||||
s.source.AddEventHandler(ctx, handler)
|
||||
}
|
90
source/nat64source_test.go
Normal file
90
source/nat64source_test.go
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package source
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/internal/testutils"
|
||||
)
|
||||
|
||||
// Validates that dedupSource is a Source
|
||||
var _ Source = &nat64Source{}
|
||||
|
||||
func TestNAT64Source(t *testing.T) {
|
||||
t.Run("Endpoints", testNat64Source)
|
||||
}
|
||||
|
||||
// testDedupEndpoints tests that duplicates from the wrapped source are removed.
|
||||
func testNat64Source(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
title string
|
||||
endpoints []*endpoint.Endpoint
|
||||
expected []*endpoint.Endpoint
|
||||
}{
|
||||
{
|
||||
"single non-nat64 ipv6 endpoint returns one ipv6 endpoint",
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8:1::1"}},
|
||||
},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8:1::1"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
"single nat64 ipv6 endpoint returns one ipv4 endpoint and one ipv6 endpoint",
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::192.0.2.42"}},
|
||||
},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::192.0.2.42"}},
|
||||
{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"192.0.2.42"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
"single nat64 ipv6 endpoint returns one ipv4 endpoint and one ipv6 endpoint",
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::c000:22a"}},
|
||||
},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::c000:22a"}},
|
||||
{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"192.0.2.42"}},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.title, func(t *testing.T) {
|
||||
mockSource := new(testutils.MockSource)
|
||||
mockSource.On("Endpoints").Return(tc.endpoints, nil)
|
||||
|
||||
// Create our object under test and get the endpoints.
|
||||
source := NewNAT64Source(mockSource, []string{"2001:DB8::/96"})
|
||||
|
||||
endpoints, err := source.Endpoints(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Validate returned endpoints against desired endpoints.
|
||||
validateEndpoints(t, endpoints, tc.expected)
|
||||
|
||||
// Validate that the mock source was called.
|
||||
mockSource.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user