go.ligato.io/vpp-agent/v3@v3.5.0/tests/e2e/120_dns_test.go (about)

     1  //  Copyright (c) 2019 Cisco and/or its affiliates.
     2  //
     3  //  Licensed under the Apache License, Version 2.0 (the "License");
     4  //  you may not use this file except in compliance with the License.
     5  //  You may obtain a copy of the License at:
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  //  Unless required by applicable law or agreed to in writing, software
    10  //  distributed under the License is distributed on an "AS IS" BASIS,
    11  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //  See the License for the specific language governing permissions and
    13  //  limitations under the License.
    14  
    15  package e2e
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"net"
    21  	"testing"
    22  	"time"
    23  
    24  	docker "github.com/fsouza/go-dockerclient"
    25  	. "github.com/onsi/gomega"
    26  
    27  	linux_interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/linux/interfaces"
    28  	linux_iptables "go.ligato.io/vpp-agent/v3/proto/ligato/linux/iptables"
    29  	vpp_dns "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/dns"
    30  	vpp_interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces"
    31  	vpp_l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3"
    32  	. "go.ligato.io/vpp-agent/v3/tests/e2e/e2etest"
    33  )
    34  
    35  // TestDnsCache tests ability of VPP to act as DNS server with cache capabilities (cache info from upstream DNS server)
    36  func TestDnsCache(t *testing.T) {
    37  	dnsIP4Result := net.ParseIP("10.100.0.1")
    38  	dnsIP6Result := net.ParseIP("fc::1") // fc::/7 is ipv6 private range (like 10.0.0.0/8 for ipv4)
    39  
    40  	cases := []struct {
    41  		Name                                 string
    42  		PublicUpstreamDNSServer              net.IP
    43  		QueryDomainName                      string
    44  		UnreachabilityVerificationDomainName string
    45  		SetupModifiers                       []SetupOptModifier
    46  		ExpectedResolvedIPv4Address          net.IP
    47  		ExpectedResolvedIPv6Address          net.IP
    48  		SkipAAAARecordCheck                  bool
    49  		SkipAll                              bool
    50  		SkipReason                           string
    51  	}{
    52  		{
    53  			Name:                                 "Test VPP DNS Cache with google DNS as upstream DNS server",
    54  			PublicUpstreamDNSServer:              net.ParseIP("8.8.8.8"),
    55  			QueryDomainName:                      "www.google.com",
    56  			UnreachabilityVerificationDomainName: "www.sme.sk",
    57  			SkipAAAARecordCheck:                  true, // TODO remove skipping when VPP bug resolved
    58  			SkipReason:                           "VPP bug https://jira.fd.io/browse/VPP-1963",
    59  		}, {
    60  			Name:                                 "Test VPP DNS Cache with cloudflare DNS as upstream DNS server",
    61  			PublicUpstreamDNSServer:              net.ParseIP("1.1.1.1"),
    62  			QueryDomainName:                      "www.google.com",
    63  			UnreachabilityVerificationDomainName: "ubuntu.com",
    64  			SkipAll:                              true, // TODO remove skipping when VPP bug resolved
    65  			SkipReason:                           "VPP bug https://jira.fd.io/browse/VPP-1963",
    66  		}, {
    67  			Name:                                 "Test VPP DNS Cache with coredns container as upstream DNS server",
    68  			QueryDomainName:                      "dnscache." + LigatoDNSHostNameSuffix,
    69  			UnreachabilityVerificationDomainName: "unresolvable." + LigatoDNSHostNameSuffix,
    70  			SetupModifiers: []SetupOptModifier{
    71  				WithDNSServer(WithZonedStaticEntries(LigatoDNSHostNameSuffix,
    72  					fmt.Sprintf("%s %s", dnsIP4Result, "dnscache."+LigatoDNSHostNameSuffix),
    73  					fmt.Sprintf("%s %s", dnsIP6Result, "dnscache."+LigatoDNSHostNameSuffix),
    74  				)),
    75  			},
    76  			ExpectedResolvedIPv4Address: dnsIP4Result,
    77  			ExpectedResolvedIPv6Address: dnsIP6Result,
    78  			SkipAll:                     true, // TODO remove skipping when VPP bug resolved
    79  			SkipReason:                  "VPP bug https://jira.fd.io/browse/VPP-1963",
    80  		},
    81  	}
    82  
    83  	// Run all cases
    84  	for _, td := range cases {
    85  		t.Run(td.Name, func(t *testing.T) {
    86  			if td.SkipAll {
    87  				t.Skipf("Skipped due to %s", td.SkipReason)
    88  			}
    89  
    90  			// test setup
    91  			td.SetupModifiers = append(td.SetupModifiers, WithCustomVPPAgent()) // need iptables to be installed
    92  			ctx := Setup(t, td.SetupModifiers...)
    93  			defer ctx.Teardown()
    94  
    95  			// start microservice
    96  			ms := ctx.StartMicroservice("microservice1", useMicroserviceWithDig())
    97  
    98  			// configure VPP-Agent container as DNS server
    99  			vppDNSServer := net.ParseIP(ctx.Agent.IPAddress())
   100  			ctx.Expect(vppDNSServer).ShouldNot(BeNil(), "VPP DNS Server container has no IP address")
   101  			upstreamDNSServer := td.PublicUpstreamDNSServer
   102  			if td.PublicUpstreamDNSServer == nil {
   103  				upstreamDNSServer = net.ParseIP(ctx.DNSServer.IPAddress())
   104  				ctx.Expect(upstreamDNSServer).ShouldNot(BeNil(),
   105  					"Local upstream DNS Server container (CoreDNS) has no IP address")
   106  			}
   107  			err := configureVPPAgentAsDNSServer(ctx, vppDNSServer, upstreamDNSServer)
   108  			ctx.Expect(err).ShouldNot(HaveOccurred(), "Configuring changes to VPP-Agent failed with err")
   109  
   110  			// Testing resolving DNS query by VPP (it should make request to upstream DNS server)
   111  			//// Testing A (IPv4) record
   112  			resolvedIPAddresses, err := ms.Dig(vppDNSServer, td.QueryDomainName, A)
   113  			ctx.Expect(err).ToNot(HaveOccurred())
   114  			if td.ExpectedResolvedIPv4Address != nil {
   115  				ctx.Expect(resolvedIPAddresses).To(ConsistOf([]net.IP{td.ExpectedResolvedIPv4Address}))
   116  			} else { // external domain have loadbalancers -> can't tell what IP address we get from upstream DNS server
   117  				ctx.Expect(resolvedIPAddresses).NotTo(BeEmpty())
   118  				ctx.Expect(resolvedIPAddresses[0].To4() != nil).Should(BeTrue(), "is not ipv4 address")
   119  			}
   120  
   121  			//// Testing AAAA (IPv6) record
   122  			if !td.SkipAAAARecordCheck {
   123  				resolvedIPAddresses, err = ms.Dig(vppDNSServer, td.QueryDomainName, AAAA)
   124  				ctx.Expect(err).ToNot(HaveOccurred())
   125  				if td.ExpectedResolvedIPv6Address != nil {
   126  					ctx.Expect(resolvedIPAddresses).To(ConsistOf([]net.IP{td.ExpectedResolvedIPv6Address}))
   127  				} else { // external domain have loadbalancers -> can't tell what IP address we get from upstream DNS server
   128  					ctx.Expect(resolvedIPAddresses).NotTo(BeEmpty())
   129  					ctx.Expect(resolvedIPAddresses[0].To4() == nil).Should(BeTrue(), "is not ipv6 address")
   130  				}
   131  			}
   132  
   133  			// block additional request to upstream DNS server (VPP should have the info
   134  			// already cached and should not need the upstream DNS server anymore)
   135  			if td.PublicUpstreamDNSServer != nil {
   136  				// block request to upstream DNS server
   137  				blockUpstreamDNSServer := &linux_iptables.RuleChain{
   138  					Name:      "blockUpstreamDNSServer",
   139  					Protocol:  linux_iptables.RuleChain_IPV4,
   140  					Table:     linux_iptables.RuleChain_FILTER,
   141  					ChainType: linux_iptables.RuleChain_FORWARD,
   142  					Rules: []string{
   143  						fmt.Sprintf("-j DROP -d %s", upstreamDNSServer.String()),
   144  					},
   145  				}
   146  				ctx.Expect(ctx.GenericClient().ChangeRequest().Update(blockUpstreamDNSServer).
   147  					Send(context.Background())).To(Succeed())
   148  			} else {
   149  				// using local container as DNS server -> the easy way how to block it is to kill it
   150  				ctx.Expect(ctx.DNSServer.Stop()).To(Succeed())
   151  			}
   152  
   153  			// verify the upstream DNS blocking (without it some mild test/container changes could introduce
   154  			// silent error when VPP will still access upstream DNS server due to ineffective(or still not
   155  			// effectively applied) blocking and therefore test of VPP caching will not test the cache at all)
   156  			ctx.Eventually(func() error {
   157  				_, err := ms.Dig(vppDNSServer, td.UnreachabilityVerificationDomainName, A)
   158  				return err
   159  			}, 3*time.Second).Should(HaveOccurred(),
   160  				"The connection to upstream DNS server is still not severed.")
   161  
   162  			// Testing resolving DNS query by VPP from its cache (upstream DNS server requests are blocked)
   163  			//// Testing A (IPv4) record
   164  			resolvedIPAddresses, err = ms.Dig(vppDNSServer, td.QueryDomainName, A)
   165  			ctx.Expect(err).ToNot(HaveOccurred())
   166  			if td.ExpectedResolvedIPv4Address != nil {
   167  				ctx.Expect(resolvedIPAddresses).To(ConsistOf([]net.IP{td.ExpectedResolvedIPv4Address}))
   168  			} else { // external domain have loadbalancers -> can't tell what IP address we get from upstream DNS server
   169  				ctx.Expect(resolvedIPAddresses).NotTo(BeEmpty())
   170  				ctx.Expect(resolvedIPAddresses[0].To4() != nil).Should(BeTrue(), "is not ipv4 address")
   171  			}
   172  
   173  			//// Testing AAAA (IPv6) record
   174  			if !td.SkipAAAARecordCheck {
   175  				resolvedIPAddresses, err = ms.Dig(vppDNSServer, td.QueryDomainName, AAAA)
   176  				ctx.Expect(err).ToNot(HaveOccurred())
   177  				if td.ExpectedResolvedIPv6Address != nil {
   178  					ctx.Expect(resolvedIPAddresses).To(ConsistOf([]net.IP{td.ExpectedResolvedIPv6Address}))
   179  				} else { // external domain have loadbalancers -> can't tell what IP address we get from upstream DNS server
   180  					ctx.Expect(resolvedIPAddresses).NotTo(BeEmpty())
   181  					ctx.Expect(resolvedIPAddresses[0].To4() == nil).Should(BeTrue(), "is not ipv6 address")
   182  				}
   183  			}
   184  		})
   185  	}
   186  }
   187  
   188  // configureVPPAgentAsDNSServer configures VPP-Agent container to act as DNS server.
   189  func configureVPPAgentAsDNSServer(ctx *TestCtx, vppDNSServer, upstreamDNSServer net.IP) error {
   190  	/* VPP-Agent as DNS Server topology:
   191  
   192  	   +--------------------------------------------+
   193  	   |                VPP-Agent container         |
   194  	   |                                            |
   195  	   |   +----------------+  +----------------+   |
   196  	   |   |    VPP-Agent   |  |      VPP       |   |
   197  	   |   |                |  |                |   |
   198  	   |   |                |  |                |   |
   199  	   |   |                |  |       + vppTap |   |
   200  	   |   |                |  |       |        |   |
   201  	   |   +------+---------+  +----------------+   |
   202  	   |          |                    |            |
   203  	   |          |                    + linuxTap   |
   204  	   |          |                    |            |
   205  	   |   +------------------------------------+   |
   206  	   |   |      |   Linux Kernel     |        |   |
   207  	   |   |  +---+--------------------+------+ |   |
   208  	   |   |  |            NAT                | |   |
   209  	   |   |  +--------------+----------------+ |   |
   210  	   |   |                 |                  |   |
   211  	   |   +------------------------------------+   |
   212  	   |                     |                      |
   213  	   +--------------------------------------------+
   214  	                         |
   215  	                         + Default container interface
   216  	*/
   217  
   218  	const (
   219  		vppTapName         = "tap1"
   220  		linuxTapName       = "tap1-host"
   221  		vppTapIP           = "10.10.0.2"
   222  		linuxTapIP         = "10.10.0.3"
   223  		vppTapIPWithMask   = vppTapIP + "/24"
   224  		linuxTapIPWithMask = linuxTapIP + "/24"
   225  	)
   226  
   227  	// configure VPP to act as DNS cache server
   228  	dnsCacheServer := &vpp_dns.DNSCache{
   229  		UpstreamDnsServers: []string{upstreamDNSServer.String()},
   230  	}
   231  
   232  	// tap tunnel from VPP to container linux environment
   233  	vppTap := &vpp_interfaces.Interface{
   234  		Name:        vppTapName,
   235  		Type:        vpp_interfaces.Interface_TAP,
   236  		Enabled:     true,
   237  		IpAddresses: []string{vppTapIPWithMask},
   238  		Link: &vpp_interfaces.Interface_Tap{
   239  			Tap: &vpp_interfaces.TapLink{
   240  				Version:    2,
   241  				HostIfName: linuxTapName,
   242  				//ToMicroservice: MsNamePrefix + msName,
   243  			},
   244  		},
   245  	}
   246  	linuxTap := &linux_interfaces.Interface{
   247  		Name:        linuxTapName,
   248  		Type:        linux_interfaces.Interface_TAP_TO_VPP,
   249  		Enabled:     true,
   250  		HostIfName:  linuxTapName,
   251  		IpAddresses: []string{linuxTapIPWithMask},
   252  		Link: &linux_interfaces.Interface_Tap{
   253  			Tap: &linux_interfaces.TapLink{
   254  				VppTapIfName: vppTapName,
   255  			},
   256  		},
   257  	}
   258  
   259  	// routing all packets out of vpp using tap tunnel
   260  	// (requesting upstream DNS server + responding to clients of VPP in role of DNS server)
   261  	vppRouteOut := &vpp_l3.Route{
   262  		DstNetwork:        "0.0.0.0/0",
   263  		NextHopAddr:       linuxTapIP,
   264  		OutgoingInterface: vppTapName,
   265  	}
   266  
   267  	// NAT translation so that VPP-Agent container IP address with standard DNS port (53) acts as DNS Server service.
   268  	// The VPP handles DNS packets from its inner tap tunnel end, but packets arrive at default container interface.
   269  	// So additional path to join these 2 places is done (tap + linux routing), but that is not enough due to
   270  	// using container ip address as DNS Server service address. The DNS packet must be forwarded to VPP, but it
   271  	// thinks that it has already arrived where it is supposed to be (external IP of container), so the destination
   272  	// address must be translated to arrive in VPP. This happens by configuring linux kernel's prerouting chain of
   273  	// NAT table. The answer to DNS request must be also translated (changed source IP) by using postrouting chain
   274  	// of NAT table. The DNS traffic should normally stay only on port 53, but VPP contacts upstream DNS server
   275  	// (to consult unknown DNS domain names) with source port 53053 and that makes trouble when answer return
   276  	// from these upstream DNS servers. Hence, the NAT also for 53053 port.
   277  	vppDNSCommunicationPrerouting := &linux_iptables.RuleChain{
   278  		Name:      "VPPDNSCommunicationPrerouting",
   279  		Protocol:  linux_iptables.RuleChain_IPV4,
   280  		Table:     linux_iptables.RuleChain_NAT,
   281  		ChainType: linux_iptables.RuleChain_PREROUTING,
   282  		Rules: []string{
   283  			// NAT for communication between client and VPP DNS server
   284  			fmt.Sprintf("-p tcp -d %s --dport 53 -j DNAT --to-destination %s", vppDNSServer.String(), vppTapIP),
   285  			fmt.Sprintf("-p udp -d %s --dport 53 -j DNAT --to-destination %s", vppDNSServer.String(), vppTapIP),
   286  			// NAT for communication with upstream DNS server
   287  			fmt.Sprintf("-p tcp -d %s --dport 53053 -j DNAT --to-destination %s", vppDNSServer.String(), vppTapIP),
   288  			fmt.Sprintf("-p udp -d %s --dport 53053 -j DNAT --to-destination %s", vppDNSServer.String(), vppTapIP),
   289  		},
   290  	}
   291  	vppDNSCommunicationPostrouting := &linux_iptables.RuleChain{
   292  		Name:      "VPPDNSCommunicationPostrouting",
   293  		Protocol:  linux_iptables.RuleChain_IPV4,
   294  		Table:     linux_iptables.RuleChain_NAT,
   295  		ChainType: linux_iptables.RuleChain_POSTROUTING,
   296  		Rules: []string{
   297  			// NAT for communication between client and VPP DNS server
   298  			fmt.Sprintf("-p tcp -s %s --sport 53 -j SNAT --to-source %s", vppTapIP, vppDNSServer.String()),
   299  			fmt.Sprintf("-p udp -s %s --sport 53 -j SNAT --to-source %s", vppTapIP, vppDNSServer.String()),
   300  			// NAT for communication with upstream DNS server
   301  			fmt.Sprintf("-p tcp -s %s --sport 53053 -j SNAT --to-source %s", vppTapIP, vppDNSServer.String()),
   302  			fmt.Sprintf("-p udp -s %s --sport 53053 -j SNAT --to-source %s", vppTapIP, vppDNSServer.String()),
   303  		},
   304  	}
   305  
   306  	// apply the configuration
   307  	req := ctx.GenericClient().ChangeRequest()
   308  	err := req.Update(
   309  		dnsCacheServer,
   310  		vppTap,
   311  		linuxTap,
   312  		vppRouteOut,
   313  		vppDNSCommunicationPrerouting,
   314  		vppDNSCommunicationPostrouting,
   315  	).Send(context.Background())
   316  	return err
   317  }
   318  
   319  // useMicroserviceWithDig provides modifier for using specialized microservice image for using linux dig tool
   320  func useMicroserviceWithDig() MicroserviceOptModifier {
   321  	return WithMSContainerStartHook(func(opts *docker.CreateContainerOptions) {
   322  		// use different image (+ entrypoint usage in image needs changes in container start)
   323  		opts.Config.Image = "itsthenetwork/alpine-dig"
   324  		opts.Config.Entrypoint = []string{"tail", "-f", "/dev/null"}
   325  		opts.Config.Cmd = []string{}
   326  		opts.HostConfig.NetworkMode = "bridge" // return back to default docker networking
   327  	})
   328  }