Skip to content

Commit

Permalink
fix wireguard endpoint dns resolution
Browse files Browse the repository at this point in the history
  • Loading branch information
aleoli authored and adamjensenbot committed Jun 16, 2022
1 parent b7cf2a2 commit d9bc02e
Show file tree
Hide file tree
Showing 7 changed files with 328 additions and 76 deletions.
16 changes: 16 additions & 0 deletions pkg/liqonet/tunnel/resolver/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2019-2022 The Liqo 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 resolver implements the resolver used to resolve hostnames to IP addresses.
package resolver
87 changes: 87 additions & 0 deletions pkg/liqonet/tunnel/resolver/resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2019-2022 The Liqo 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 resolver

import (
"context"
"fmt"
"net"
"sync"
)

var (
resolverFunc = net.DefaultResolver.LookupIPAddr
resolveCache = sync.Map{}
)

// Resolve resolves the given hostname to an IP address.
// It prefers IPv4 addresses if available, and falls back to IPv6 addresses if not.
// If the hostname was already resolved and it is still valid, it returns the cached IP address.
func Resolve(ctx context.Context, address string) (*net.IPAddr, error) {
if ip := net.ParseIP(address); ip != nil {
return &net.IPAddr{IP: ip}, nil
}

ipAddrs, err := resolverFunc(ctx, address)
if err != nil {
return nil, err
}

ipv4List := []*net.IPAddr{}
ipv6List := []*net.IPAddr{}

for i := range ipAddrs {
ip := ipAddrs[i]
if ipv4 := ip.IP.To4(); ipv4 != nil {
ipv4List = append(ipv4List, &ip)
}
if ipv6 := ip.IP.To16(); ipv6 != nil {
ipv6List = append(ipv6List, &ip)
}
}

if len(ipv4List) > 0 {
if ip := lookupCache(address, ipv4List); ip != nil {
return ip, nil
}
resolveCache.Store(address, ipv4List[0])
return ipv4List[0], nil
}
if len(ipv6List) > 0 {
if ip := lookupCache(address, ipv6List); ip != nil {
return ip, nil
}
resolveCache.Store(address, ipv6List[0])
return ipv6List[0], nil
}

return nil, fmt.Errorf("no IP addresses found for %q", address)
}

func lookupCache(hostname string, resolvedIPs []*net.IPAddr) *net.IPAddr {
v, found := resolveCache.Load(hostname)
if !found {
return nil
}
cachedIP := v.(*net.IPAddr)

for _, resIP := range resolvedIPs {
if resIP.IP.Equal(cachedIP.IP) {
return cachedIP
}
}

return nil
}
27 changes: 27 additions & 0 deletions pkg/liqonet/tunnel/resolver/resolver_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2019-2022 The Liqo 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 resolver

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestResolver(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Resolver Suite")
}
184 changes: 184 additions & 0 deletions pkg/liqonet/tunnel/resolver/resolver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Copyright 2019-2022 The Liqo 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 resolver

import (
"context"
"net"
"sync"

. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
)

var _ = Describe("Resolver", func() {

var (
newSyncMap = func(m map[string]*net.IPAddr, sm *sync.Map) {
sm.Range(func(key, value interface{}) bool {
sm.Delete(key)
return true
})
for k, v := range m {
sm.Store(k, v)
}
}

newMap = func(sm *sync.Map) map[string]*net.IPAddr {
res := map[string]*net.IPAddr{}
sm.Range(func(k, v interface{}) bool {
res[k.(string)] = v.(*net.IPAddr)
return true
})
return res
}
)

var (
ctx context.Context
)

BeforeEach(func() {
ctx = context.Background()
})

type resolveTestcase struct {
name string
resolver func(ctx context.Context, host string) ([]net.IPAddr, error)
cache map[string]*net.IPAddr
expectedIP string
expectedCache OmegaMatcher
}

DescribeTable("Resolve",

func(c *resolveTestcase) {
newSyncMap(c.cache, &resolveCache)
resolverFunc = c.resolver
ip, err := Resolve(ctx, c.name)
Expect(err).ToNot(HaveOccurred())
Expect(ip.String()).To(Equal(c.expectedIP))
Expect(newMap(&resolveCache)).To(c.expectedCache)
},

Entry("should handle an IP name", &resolveTestcase{
name: "1.2.3.4",
resolver: nil,
cache: map[string]*net.IPAddr{},
expectedIP: "1.2.3.4",
expectedCache: BeEmpty(),
}),

Entry("should resolve IPv4 addresses", &resolveTestcase{
name: "example.com",
resolver: func(ctx context.Context, host string) ([]net.IPAddr, error) {
return []net.IPAddr{
{IP: net.IPv4(1, 2, 3, 4)},
{IP: net.IPv4(1, 2, 3, 5)},
}, nil
},
cache: map[string]*net.IPAddr{},
expectedIP: "1.2.3.4",
expectedCache: HaveKeyWithValue("example.com", &net.IPAddr{IP: net.IPv4(1, 2, 3, 4)}),
}),

Entry("should resolve IPv4 address with cache", &resolveTestcase{
name: "example.com",
resolver: func(ctx context.Context, host string) ([]net.IPAddr, error) {
return []net.IPAddr{
{IP: net.IPv4(1, 2, 3, 4)},
{IP: net.IPv4(1, 2, 3, 5)},
}, nil
},
cache: map[string]*net.IPAddr{
"example.com": {IP: net.IPv4(1, 2, 3, 5)},
},
expectedIP: "1.2.3.5",
expectedCache: HaveKeyWithValue("example.com", &net.IPAddr{IP: net.IPv4(1, 2, 3, 5)}),
}),

Entry("should resolve IPv6 addresses", &resolveTestcase{
name: "example.com",
resolver: func(ctx context.Context, host string) ([]net.IPAddr, error) {
return []net.IPAddr{
{IP: net.ParseIP("2001:db8:a0b:12f0::1")},
{IP: net.ParseIP("2001:db8:a0b:12f0::2")},
}, nil
},
cache: map[string]*net.IPAddr{},
expectedIP: "2001:db8:a0b:12f0::1",
expectedCache: HaveKeyWithValue("example.com", &net.IPAddr{IP: net.ParseIP("2001:db8:a0b:12f0::1")}),
}),

Entry("should resolve IPv6 address with cache", &resolveTestcase{
name: "example.com",
resolver: func(ctx context.Context, host string) ([]net.IPAddr, error) {
return []net.IPAddr{
{IP: net.ParseIP("2001:db8:a0b:12f0::1")},
{IP: net.ParseIP("2001:db8:a0b:12f0::2")},
}, nil
},
cache: map[string]*net.IPAddr{
"example.com": {IP: net.ParseIP("2001:db8:a0b:12f0::2")},
},
expectedIP: "2001:db8:a0b:12f0::2",
expectedCache: HaveKeyWithValue("example.com", &net.IPAddr{IP: net.ParseIP("2001:db8:a0b:12f0::2")}),
}),

Entry("should prefer IPv4 address", &resolveTestcase{
name: "example.com",
resolver: func(ctx context.Context, host string) ([]net.IPAddr, error) {
return []net.IPAddr{
{IP: net.ParseIP("2001:db8:a0b:12f0::1")},
{IP: net.IPv4(1, 2, 3, 4)},
}, nil
},
cache: map[string]*net.IPAddr{},
expectedIP: "1.2.3.4",
expectedCache: HaveKeyWithValue("example.com", &net.IPAddr{IP: net.IPv4(1, 2, 3, 4)}),
}),

Entry("should prefer IPv4 address with cache", &resolveTestcase{
name: "example.com",
resolver: func(ctx context.Context, host string) ([]net.IPAddr, error) {
return []net.IPAddr{
{IP: net.ParseIP("2001:db8:a0b:12f0::1")},
{IP: net.IPv4(1, 2, 3, 4)},
}, nil
},
cache: map[string]*net.IPAddr{
"example.com": {IP: net.ParseIP("2001:db8:a0b:12f0::1")},
},
expectedIP: "1.2.3.4",
expectedCache: HaveKeyWithValue("example.com", &net.IPAddr{IP: net.IPv4(1, 2, 3, 4)}),
}),

Entry("should update cache if entry is no more valid", &resolveTestcase{
name: "example.com",
resolver: func(ctx context.Context, host string) ([]net.IPAddr, error) {
return []net.IPAddr{
{IP: net.IPv4(1, 2, 3, 5)},
}, nil
},
cache: map[string]*net.IPAddr{
"example.com": {IP: net.IPv4(1, 2, 3, 4)},
},
expectedIP: "1.2.3.5",
expectedCache: HaveKeyWithValue("example.com", &net.IPAddr{IP: net.IPv4(1, 2, 3, 5)}),
}),
)

})
30 changes: 6 additions & 24 deletions pkg/liqonet/tunnel/wireguard/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
netv1alpha1 "github.com/liqotech/liqo/apis/net/v1alpha1"
liqoconst "github.com/liqotech/liqo/pkg/consts"
"github.com/liqotech/liqo/pkg/liqonet/tunnel"
"github.com/liqotech/liqo/pkg/liqonet/tunnel/resolver"
"github.com/liqotech/liqo/pkg/liqonet/utils"
)

Expand Down Expand Up @@ -73,7 +74,7 @@ type wgConfig struct {

// ResolverFunc type of function that knows how to resolve an ip address belonging to
// ipv4 or ipv6 family.
type ResolverFunc func(network string, address string) (*net.IPAddr, error)
type ResolverFunc func(address string) (*net.IPAddr, error)

// Wireguard a wrapper for the wireguard device and its configuration.
type Wireguard struct {
Expand Down Expand Up @@ -164,7 +165,9 @@ func (w *Wireguard) ConnectToEndpoint(tep *netv1alpha1.TunnelEndpoint) (*netv1al
}

// parse remote endpoint.
endpoint, err := getEndpoint(tep, net.ResolveIPAddr)
endpoint, err := getEndpoint(tep, func(address string) (*net.IPAddr, error) {
return resolver.Resolve(context.TODO(), address)
})
if err != nil {
return newConnectionOnError(err.Error()), err
}
Expand Down Expand Up @@ -395,28 +398,7 @@ func getTunnelPortFromTep(tep *netv1alpha1.TunnelEndpoint) (int, error) {
}

func getTunnelAddressFromTep(tep *netv1alpha1.TunnelEndpoint, addrResolver ResolverFunc) (*net.IPAddr, error) {
protocolFamilies := map[string]string{"ipv4": "ip4", "ipv6": "ip6"}
clusterID := tep.Spec.ClusterID
tepName := tep.Name
endpoint := tep.Spec.EndpointIP

// For each protocol family we try to get the endpoint ip address.
// After the first match we return otherwise we continue.
for pfKey, pfValue := range protocolFamilies {
// Get endpoint ip, first we assume the ip address belongs to the ipv4 protocol family.
klog.V(4).Infof("%s -> trying to retrieve endpoint address {%s} from tunnelendpoint "+
"resource {%s} as {%s} address", clusterID, endpoint, tepName, pfKey)
tunnelAddress, err := addrResolver(pfValue, tep.Spec.EndpointIP)
if err != nil {
klog.V(4).Infof("%s -> unable to retrieve the endpoint address {%s}, "+
"found in tunnelendpoint resource {%s}, as an {%s} address: %v", clusterID, endpoint, tepName, pfKey, err)
} else {
klog.V(4).Infof("%s -> successfully retrieved endpoint address {%s} from tunnelendpoint resource "+
"{%s} as {%s} address", clusterID, tepName, endpoint, pfKey)
return tunnelAddress, nil
}
}
return nil, fmt.Errorf(" endpoint address {%s} is neither an ipv4 address nor an ipv6 one", endpoint)
return addrResolver(tep.Spec.EndpointIP)
}

func newConnectionOnError(msg string) *netv1alpha1.Connection {
Expand Down

0 comments on commit d9bc02e

Please sign in to comment.