github.com/google/cloudprober@v0.11.3/probes/ping/ping_test.go (about)

     1  // Copyright 2017-2020 The Cloudprober Authors.
     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 ping
    16  
    17  import (
    18  	"fmt"
    19  	"net"
    20  	"reflect"
    21  	"strings"
    22  	"sync"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/golang/glog"
    27  	"github.com/golang/protobuf/proto"
    28  	"github.com/google/cloudprober/probes/options"
    29  	configpb "github.com/google/cloudprober/probes/ping/proto"
    30  	"github.com/google/cloudprober/targets"
    31  	"github.com/google/cloudprober/targets/endpoint"
    32  	"golang.org/x/net/icmp"
    33  	"golang.org/x/net/ipv4"
    34  	"golang.org/x/net/ipv6"
    35  )
    36  
    37  func peerToIP(peer net.Addr) string {
    38  	switch peer := peer.(type) {
    39  	case *net.UDPAddr:
    40  		return peer.IP.String()
    41  	case *net.IPAddr:
    42  		return peer.IP.String()
    43  	}
    44  	return ""
    45  }
    46  
    47  // replyPkt creates an ECHO reply packet from the ECHO request packet.
    48  func replyPkt(pkt []byte, ipVersion int) []byte {
    49  	protocol := protocolICMP
    50  	var typ icmp.Type
    51  	typ = ipv4.ICMPTypeEchoReply
    52  	if ipVersion == 6 {
    53  		protocol = protocolIPv6ICMP
    54  		typ = ipv6.ICMPTypeEchoReply
    55  	}
    56  	m, _ := icmp.ParseMessage(protocol, pkt)
    57  	m.Type = typ
    58  	b, _ := m.Marshal(nil)
    59  	return b
    60  }
    61  
    62  // testICMPConn implements the icmpConn interface.
    63  // It implements the following packets pipeline:
    64  //      write(packet) --> sentPackets channel -> read() -> packet
    65  // It has a per-target channel that receives packets through the "write" call.
    66  // "read" call fetches packets from that channel and returns them to the
    67  // caller.
    68  type testICMPConn struct {
    69  	sentPackets map[string](chan []byte)
    70  	c           *configpb.ProbeConf
    71  	ipVersion   int
    72  
    73  	flipLastByte   bool
    74  	flipLastByteMu sync.Mutex
    75  }
    76  
    77  func newTestICMPConn(opts *options.Options, targets []endpoint.Endpoint) *testICMPConn {
    78  	tic := &testICMPConn{
    79  		c:           opts.ProbeConf.(*configpb.ProbeConf),
    80  		ipVersion:   opts.IPVersion,
    81  		sentPackets: make(map[string](chan []byte)),
    82  	}
    83  	for _, target := range targets {
    84  		tic.sentPackets[target.Name] = make(chan []byte, tic.c.GetPacketsPerProbe())
    85  	}
    86  	return tic
    87  }
    88  
    89  func (tic *testICMPConn) setFlipLastByte() {
    90  	tic.flipLastByteMu.Lock()
    91  	defer tic.flipLastByteMu.Unlock()
    92  	tic.flipLastByte = true
    93  }
    94  
    95  func (tic *testICMPConn) read(buf []byte) (int, net.Addr, time.Time, error) {
    96  	// We create per-target select cases, with each target's select-case
    97  	// pointing to that target's sentPackets channel.
    98  	var cases []reflect.SelectCase
    99  	var targets []string
   100  	for t, ch := range tic.sentPackets {
   101  		cases = append(cases, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch)})
   102  		targets = append(targets, t)
   103  	}
   104  
   105  	// Select over the select cases.
   106  	chosen, value, ok := reflect.Select(cases)
   107  	if !ok {
   108  		return 0, nil, time.Now(), fmt.Errorf("nothing to read")
   109  	}
   110  
   111  	pkt := value.Bytes()
   112  
   113  	// Since we are echoing the packets, copy the received packet into the
   114  	// provided buffer.
   115  	respPkt := replyPkt(pkt, tic.ipVersion)
   116  	tic.flipLastByteMu.Lock()
   117  	if tic.flipLastByte {
   118  		lastByte := ^respPkt[len(respPkt)-1]
   119  		respPkt = append(respPkt[:len(respPkt)-1], lastByte)
   120  	}
   121  	tic.flipLastByteMu.Unlock()
   122  
   123  	copy(buf[0:len(pkt)], respPkt)
   124  	peerIP := net.ParseIP(targets[chosen])
   125  
   126  	var peer net.Addr
   127  	peer = &net.IPAddr{IP: peerIP}
   128  	if tic.c.GetUseDatagramSocket() {
   129  		peer = &net.UDPAddr{IP: peerIP}
   130  	}
   131  	return len(pkt), peer, time.Now(), nil
   132  }
   133  
   134  // write simply queues packets into the sentPackets channel. These packets are
   135  // retrieved by the "read" call.
   136  func (tic *testICMPConn) write(in []byte, peer net.Addr) (int, error) {
   137  	target := peerToIP(peer)
   138  
   139  	// Copy incoming bytes slice and store in the internal channel for use
   140  	// during the read call.
   141  	b := make([]byte, len(in))
   142  	copy(b, in)
   143  	tic.sentPackets[target] <- b
   144  
   145  	return len(b), nil
   146  }
   147  
   148  func (tic *testICMPConn) setReadDeadline(deadline time.Time) {
   149  }
   150  
   151  func (tic *testICMPConn) close() {
   152  }
   153  
   154  // Sends packets and verifies
   155  func sendAndCheckPackets(p *Probe, t *testing.T) {
   156  	tic := newTestICMPConn(p.opts, p.targets)
   157  	p.conn = tic
   158  	trackerChan := make(chan bool, int(p.c.GetPacketsPerProbe())*len(p.targets))
   159  	runID := p.newRunID()
   160  	p.sendPackets(runID, trackerChan)
   161  
   162  	protocol := protocolICMP
   163  	var expectedMsgType icmp.Type
   164  	expectedMsgType = ipv4.ICMPTypeEcho
   165  	if p.opts.IPVersion == 6 {
   166  		protocol = protocolIPv6ICMP
   167  		expectedMsgType = ipv6.ICMPTypeEchoRequest
   168  	}
   169  
   170  	for _, ep := range p.targets {
   171  		target := ep.Name
   172  		if int(p.results[target].sent) != int(p.c.GetPacketsPerProbe()) {
   173  			t.Errorf("Mismatch in number of packets recorded to be sent. Sent: %d, Recorded: %d", p.c.GetPacketsPerProbe(), p.results[target].sent)
   174  		}
   175  		if len(tic.sentPackets[target]) != int(p.c.GetPacketsPerProbe()) {
   176  			t.Errorf("Mismatch in number of packets received. Sent: %d, Got: %d", p.c.GetPacketsPerProbe(), len(tic.sentPackets[target]))
   177  		}
   178  		close(tic.sentPackets[target])
   179  		for b := range tic.sentPackets[target] {
   180  			// Make sure packets parse ok
   181  			m, err := icmp.ParseMessage(protocol, b)
   182  			if err != nil {
   183  				t.Errorf("%v", err)
   184  			}
   185  			// Check packet type
   186  			if m.Type != expectedMsgType {
   187  				t.Errorf("Wrong packet type. Got: %v, expected: %v", m.Type, expectedMsgType)
   188  			}
   189  			// Check packet size
   190  			if len(b) != int(p.c.GetPayloadSize())+8 {
   191  				t.Errorf("Wrong packet size. Got: %d, expected: %d", len(b), int(p.c.GetPayloadSize())+8)
   192  			}
   193  			// Verify ICMP id and sequence number
   194  			pkt, ok := m.Body.(*icmp.Echo)
   195  			if !ok {
   196  				t.Errorf("Wrong ICMP packet body")
   197  			}
   198  			if pkt.ID != int(runID) {
   199  				t.Errorf("Got wrong ICMP ID. Got: %d, Expected: %d", pkt.ID, runID)
   200  			}
   201  			if pkt.Seq&0xff00 != int(runID)&0xff00 {
   202  				t.Errorf("Got wrong ICMP base seq number. Got: %d, Expected: %d", pkt.Seq&0xff00, runID&0xff00)
   203  			}
   204  		}
   205  	}
   206  }
   207  
   208  func newProbe(c *configpb.ProbeConf, ipVersion int, t []string) (*Probe, error) {
   209  	p := &Probe{
   210  		name: "ping_test",
   211  		opts: &options.Options{
   212  			ProbeConf: c,
   213  			Targets:   targets.StaticTargets(strings.Join(t, ",")),
   214  			Interval:  2 * time.Second,
   215  			Timeout:   time.Second,
   216  			IPVersion: ipVersion,
   217  		},
   218  	}
   219  	return p, p.initInternal()
   220  }
   221  
   222  // Test sendPackets IPv4, raw sockets
   223  func TestSendPackets(t *testing.T) {
   224  	p, err := newProbe(&configpb.ProbeConf{}, 0, []string{"2.2.2.2", "3.3.3.3"})
   225  	if err != nil {
   226  		t.Fatalf("Got error from newProbe: %v", err)
   227  	}
   228  	sendAndCheckPackets(p, t)
   229  }
   230  
   231  // Test sendPackets IPv6, raw sockets
   232  func TestSendPacketsIPv6(t *testing.T) {
   233  	p, err := newProbe(&configpb.ProbeConf{}, 6, []string{"::2", "::3"})
   234  	if err != nil {
   235  		t.Fatalf("Got error from newProbe: %v", err)
   236  	}
   237  	sendAndCheckPackets(p, t)
   238  }
   239  
   240  // Test sendPackets IPv6, raw sockets, no packets should come on IPv4 target
   241  func TestSendPacketsIPv6ToIPv4Hosts(t *testing.T) {
   242  	c := &configpb.ProbeConf{}
   243  	p, err := newProbe(&configpb.ProbeConf{}, 6, []string{"2.2.2.2"})
   244  	if err != nil {
   245  		t.Fatalf("Got error from newProbe: %v", err)
   246  	}
   247  	tic := newTestICMPConn(p.opts, p.targets)
   248  	p.conn = tic
   249  	trackerChan := make(chan bool, int(c.GetPacketsPerProbe())*len(p.targets))
   250  	p.sendPackets(p.newRunID(), trackerChan)
   251  	for _, target := range p.targets {
   252  		if len(tic.sentPackets[target.Name]) != 0 {
   253  			t.Errorf("IPv6 probe: should not have received any packets for IPv4 only targets, but got %d packets", len(tic.sentPackets[target.Name]))
   254  		}
   255  	}
   256  }
   257  
   258  // Test sendPackets IPv4, datagram sockets
   259  func TestSendPacketsDatagramSocket(t *testing.T) {
   260  	c := &configpb.ProbeConf{}
   261  	c.UseDatagramSocket = proto.Bool(true)
   262  	p, err := newProbe(c, 0, []string{"2.2.2.2", "3.3.3.3"})
   263  	if err != nil {
   264  		t.Fatalf("Got error from newProbe: %v", err)
   265  	}
   266  	sendAndCheckPackets(p, t)
   267  }
   268  
   269  // Test sendPackets IPv6, datagram sockets
   270  func TestSendPacketsIPv6DatagramSocket(t *testing.T) {
   271  	p, err := newProbe(&configpb.ProbeConf{UseDatagramSocket: proto.Bool(true)}, 6, []string{"::2", "::3"})
   272  	if err != nil {
   273  		t.Fatalf("Got error from newProbe: %v", err)
   274  	}
   275  	sendAndCheckPackets(p, t)
   276  }
   277  
   278  // Test runProbe IPv4, raw sockets
   279  func testRunProbe(t *testing.T, ipVersion int, useDatagramSocket bool, payloadSize int) {
   280  	t.Helper()
   281  
   282  	c := &configpb.ProbeConf{
   283  		UseDatagramSocket: proto.Bool(useDatagramSocket),
   284  	}
   285  
   286  	// if payloadSize is non-zero, set it in config.
   287  	if payloadSize != 0 {
   288  		c.PayloadSize = proto.Int32(int32(payloadSize))
   289  	}
   290  
   291  	var targets []string
   292  
   293  	if ipVersion == 4 {
   294  		targets = []string{"2.2.2.2", "3.3.3.3", "4.4.4.4"}
   295  	} else {
   296  		targets = []string{"::2", "::3", "::4"}
   297  	}
   298  
   299  	p, err := newProbe(c, ipVersion, targets)
   300  	if err != nil {
   301  		t.Fatalf("Got error from newProbe: %v", err)
   302  	}
   303  
   304  	p.conn = newTestICMPConn(p.opts, p.targets)
   305  	p.runProbe()
   306  	for _, ep := range p.targets {
   307  		target := ep.Name
   308  
   309  		glog.Infof("target: %s, sent: %d, received: %d, total_rtt: %s", target, p.results[target].sent, p.results[target].rcvd, p.results[target].latency)
   310  		if p.results[target].sent == 0 || (p.results[target].sent != p.results[target].rcvd) {
   311  			t.Errorf("We are leaking packets. Sent: %d, Received: %d", p.results[target].sent, p.results[target].rcvd)
   312  		}
   313  	}
   314  }
   315  
   316  func testRunProbeWithMultipleSizes(t *testing.T, ipVersion int, useDatagramSocket bool) {
   317  	t.Helper()
   318  
   319  	for _, size := range []int{8, 56, 256, 1024, maxPacketSize - icmpHeaderSize} {
   320  		t.Logf("Running probe with IP%d, with useDatagramSocket: %v, payloadSize: %d", ipVersion, useDatagramSocket, size)
   321  		testRunProbe(t, ipVersion, useDatagramSocket, size)
   322  	}
   323  }
   324  
   325  // Test runProbe IPv4, raw sockets
   326  func TestRunProbe(t *testing.T) {
   327  	testRunProbeWithMultipleSizes(t, 4, false)
   328  }
   329  
   330  // Test runProbe IPv6, raw sockets
   331  func TestRunProbeIPv6(t *testing.T) {
   332  	testRunProbeWithMultipleSizes(t, 6, false)
   333  }
   334  
   335  // Test runProbe IPv4, datagram sockets
   336  func TestRunProbeDatagram(t *testing.T) {
   337  	testRunProbeWithMultipleSizes(t, 4, true)
   338  }
   339  
   340  // Test runProbe IPv6, datagram sockets
   341  func TestRunProbeIPv6Datagram(t *testing.T) {
   342  	testRunProbeWithMultipleSizes(t, 6, true)
   343  
   344  }
   345  
   346  func TestDataIntegrityValidation(t *testing.T) {
   347  	p, err := newProbe(&configpb.ProbeConf{}, 0, []string{"2.2.2.2", "3.3.3.3"})
   348  	if err != nil {
   349  		t.Fatalf("Got error from newProbe: %v", err)
   350  	}
   351  	tic := newTestICMPConn(p.opts, p.targets)
   352  	p.conn = tic
   353  
   354  	p.runProbe()
   355  
   356  	// We'll use sent and rcvd to take a snapshot of the probe counters.
   357  	sent := make(map[string]int64)
   358  	rcvd := make(map[string]int64)
   359  	for _, ep := range p.targets {
   360  		target := ep.Name
   361  
   362  		sent[target] = p.results[target].sent
   363  		rcvd[target] = p.results[target].rcvd
   364  
   365  		glog.Infof("target: %s, sent: %d, received: %d, total_rtt: %s", target, sent[target], rcvd[target], p.results[target].latency)
   366  		if sent[target] == 0 || (sent[target] != rcvd[target]) {
   367  			t.Errorf("We are leaking packets. Sent: %d, Received: %d", sent[target], rcvd[target])
   368  		}
   369  	}
   370  
   371  	// Set the test icmp connection to flip the last byte.
   372  	tic.setFlipLastByte()
   373  
   374  	// Run probe again, this time we should see data integrity validation failures.
   375  	p.runProbe()
   376  	for _, ep := range p.targets {
   377  		target := ep.Name
   378  
   379  		glog.Infof("target: %s, sent: %d, received: %d, total_rtt: %s", target, p.results[target].sent, p.results[target].rcvd, p.results[target].latency)
   380  
   381  		// Verify that we didn't increased the received counter.
   382  		if p.results[target].rcvd != rcvd[target] {
   383  			t.Errorf("Unexpected change in received packets. Got: %d, Expected: %d", p.results[target].rcvd, rcvd[target])
   384  		}
   385  
   386  		// Verify that we increased the validation failure counter.
   387  		expectedFailures := p.results[target].sent - p.results[target].rcvd
   388  		gotFailures := p.results[target].validationFailure.GetKey(dataIntegrityKey).Int64()
   389  		if gotFailures != expectedFailures {
   390  			t.Errorf("p.results[%s].validationFailure.GetKey(%s)=%d, expected=%d", target, dataIntegrityKey, gotFailures, expectedFailures)
   391  		}
   392  	}
   393  }