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 }