gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/packetimpact/tests/generic_dgram_socket_send_recv_test.go (about) 1 // Copyright 2021 The gVisor 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 generic_dgram_socket_send_recv_test 16 17 import ( 18 "context" 19 "flag" 20 "fmt" 21 "net" 22 "testing" 23 "time" 24 25 "github.com/google/go-cmp/cmp" 26 "golang.org/x/sys/unix" 27 "gvisor.dev/gvisor/pkg/tcpip" 28 "gvisor.dev/gvisor/pkg/tcpip/header" 29 "gvisor.dev/gvisor/test/packetimpact/testbench" 30 ) 31 32 const ( 33 // Even though sockets allow larger datagrams we don't test it here as they 34 // need to be fragmented and written out as individual frames. 35 36 maxICMPv4PayloadSize = header.IPv4MinimumMTU - header.EthernetMinimumSize - header.IPv4MinimumSize - header.ICMPv4MinimumSize 37 maxICMPv6PayloadSize = header.IPv6MinimumMTU - header.EthernetMinimumSize - header.IPv6MinimumSize - header.ICMPv6MinimumSize 38 maxUDPv4PayloadSize = header.IPv4MinimumMTU - header.EthernetMinimumSize - header.IPv4MinimumSize - header.UDPMinimumSize 39 maxUDPv6PayloadSize = header.IPv6MinimumMTU - header.EthernetMinimumSize - header.IPv6MinimumSize - header.UDPMinimumSize 40 ) 41 42 func maxUDPPayloadSize(addr net.IP) int { 43 if addr.To4() != nil { 44 return maxUDPv4PayloadSize 45 } 46 return maxUDPv6PayloadSize 47 } 48 49 func init() { 50 testbench.Initialize(flag.CommandLine) 51 } 52 53 func expectedEthLayer(t *testing.T, dut testbench.DUT, socketFD int32, sendTo net.IP) testbench.Layer { 54 t.Helper() 55 dst := func() tcpip.LinkAddress { 56 if isBroadcast(dut, sendTo) { 57 dut.SetSockOptInt(t, socketFD, unix.SOL_SOCKET, unix.SO_BROADCAST, 1) 58 59 // When sending to broadcast (subnet or limited), the expected ethernet 60 // address is also broadcast. 61 return header.EthernetBroadcastAddress 62 } 63 if sendTo.IsMulticast() { 64 if sendTo4 := sendTo.To4(); sendTo4 != nil { 65 return header.EthernetAddressFromMulticastIPv4Address(tcpip.AddrFrom4Slice(sendTo4)) 66 } 67 return header.EthernetAddressFromMulticastIPv6Address(tcpip.AddrFrom16Slice(sendTo.To16())) 68 } 69 return "" 70 }() 71 var ether testbench.Ether 72 if len(dst) != 0 { 73 ether.DstAddr = &dst 74 } 75 return ðer 76 } 77 78 type protocolTest interface { 79 Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) 80 Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) 81 } 82 83 func TestICMPv4(t *testing.T) { 84 runAllCombinations(t, &icmpV4Test{}) 85 } 86 87 func TestICMPv6(t *testing.T) { 88 runAllCombinations(t, &icmpV6Test{}) 89 } 90 91 func TestUDP(t *testing.T) { 92 runAllCombinations(t, &udpTest{}) 93 } 94 95 func runAllCombinations(t *testing.T, proto protocolTest) { 96 dut := testbench.NewDUT(t) 97 subnetBroadcast := dut.Net.SubnetBroadcast() 98 // Test every combination of bound/unbound, broadcast/multicast/unicast 99 // bound/destination address, and bound/not-bound to device. 100 for _, bindTo := range []net.IP{ 101 nil, // Do not bind. 102 net.IPv4zero, 103 net.IPv4bcast, 104 net.IPv4allsys, 105 net.IPv6zero, 106 subnetBroadcast, 107 dut.Net.RemoteIPv4, 108 dut.Net.RemoteIPv6, 109 } { 110 t.Run(fmt.Sprintf("bindTo=%s", bindTo), func(t *testing.T) { 111 for _, sendTo := range []net.IP{ 112 net.IPv4bcast, 113 net.IPv4allsys, 114 subnetBroadcast, 115 dut.Net.LocalIPv4, 116 dut.Net.LocalIPv6, 117 dut.Net.RemoteIPv4, 118 dut.Net.RemoteIPv6, 119 } { 120 t.Run(fmt.Sprintf("sendTo=%s", sendTo), func(t *testing.T) { 121 for _, bindToDevice := range []bool{true, false} { 122 t.Run(fmt.Sprintf("bindToDevice=%t", bindToDevice), func(t *testing.T) { 123 t.Run("Send", func(t *testing.T) { 124 proto.Send(t, dut, bindTo, sendTo, bindToDevice) 125 }) 126 t.Run("Receive", func(t *testing.T) { 127 proto.Receive(t, dut, bindTo, sendTo, bindToDevice) 128 }) 129 }) 130 } 131 }) 132 } 133 }) 134 } 135 } 136 137 type icmpV4TestEnv struct { 138 socketFD int32 139 ident uint16 140 conn testbench.IPv4Conn 141 layers testbench.Layers 142 } 143 144 type icmpV4Test struct{} 145 146 func (test *icmpV4Test) setup(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) icmpV4TestEnv { 147 t.Helper() 148 149 // Tell the DUT to create a socket. 150 var socketFD int32 151 var ident uint16 152 153 if bindTo != nil { 154 socketFD, ident = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_ICMP, bindTo) 155 } else { 156 // An unbound socket will auto-bind to INADDR_ANY. 157 socketFD = dut.Socket(t, unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_ICMP) 158 } 159 t.Cleanup(func() { 160 dut.Close(t, socketFD) 161 }) 162 163 if bindToDevice { 164 dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName)) 165 } 166 167 // Create a socket on the test runner. 168 conn := dut.Net.NewIPv4Conn(t, testbench.IPv4{}, testbench.IPv4{}) 169 t.Cleanup(func() { 170 conn.Close(t) 171 }) 172 173 dstAddr := sendTo.To4() 174 return icmpV4TestEnv{ 175 socketFD: socketFD, 176 ident: ident, 177 conn: conn, 178 layers: testbench.Layers{ 179 expectedEthLayer(t, dut, socketFD, sendTo), 180 &testbench.IPv4{ 181 DstAddr: &dstAddr, 182 }, 183 }, 184 } 185 } 186 187 func (test *icmpV4Test) Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) { 188 if bindTo.To4() == nil || isBroadcastOrMulticast(dut, bindTo) { 189 // ICMPv4 sockets cannot bind to IPv6, broadcast, or multicast 190 // addresses. 191 return 192 } 193 194 isV4 := sendTo.To4() != nil 195 196 // TODO(gvisor.dev/issue/5681): Remove this case once ICMP sockets allow 197 // sending to broadcast and multicast addresses. 198 if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && isV4 && isBroadcastOrMulticast(dut, sendTo) { 199 // expectPacket cannot be false. In some cases the packet will send, but 200 // with IPv4 destination incorrectly set to RemoteIPv4. It's all bad and 201 // not worth the effort to create a special case when this occurs. 202 t.Skip("TODO(gvisor.dev/issue/5681): Allow sending to broadcast and multicast addresses with ICMP sockets.") 203 } 204 205 expectNetworkUnreachable := true 206 // We don't expect ENETUNREACH if any of the follwing is true: 207 // 1. bindTo is specfied. 208 if !bindTo.Equal(net.IPv4zero) { 209 expectNetworkUnreachable = false 210 } 211 // 2. We are binding to a device. 212 if bindToDevice { 213 expectNetworkUnreachable = false 214 } 215 // 3. sendTo is neither 224.0.0.1 nor 255.255.255.255. 216 if !sendTo.Equal(net.IPv4bcast) && !sendTo.Equal(net.IPv4allsys) { 217 expectNetworkUnreachable = false 218 } 219 220 expectPacket := true 221 // We don't expect an incoming packet if any of the following is true: 222 // 1. sendTo is not an ipv4 address. 223 if !isV4 { 224 expectPacket = false 225 } 226 // 2. sendTo is the dut itself. 227 if sendTo.Equal(dut.Net.RemoteIPv4) { 228 expectPacket = false 229 } 230 // 3. we are expecting ENETUNREACH. 231 if expectNetworkUnreachable { 232 expectPacket = false 233 } 234 235 env := test.setup(t, dut, bindTo, sendTo, bindToDevice) 236 237 for name, payload := range map[string][]byte{ 238 "empty": nil, 239 "small": []byte("hello world"), 240 "random1k": testbench.GenerateRandomPayload(t, maxICMPv4PayloadSize), 241 } { 242 t.Run(name, func(t *testing.T) { 243 icmpLayer := &testbench.ICMPv4{ 244 Type: testbench.ICMPv4Type(header.ICMPv4Echo), 245 Payload: payload, 246 } 247 bytes, err := icmpLayer.ToBytes() 248 if err != nil { 249 t.Fatalf("icmpLayer.ToBytes() = %s", err) 250 } 251 destSockaddr := unix.SockaddrInet4{} 252 copy(destSockaddr.Addr[:], sendTo.To4()) 253 254 // Tell the DUT to send a packet out the ICMP socket. 255 ret, err := dut.SendToWithErrno(context.Background(), t, env.socketFD, bytes, 0, &destSockaddr) 256 if expectNetworkUnreachable { 257 if !(ret == -1 && err == unix.ENETUNREACH) { 258 t.Fatalf("got dut.SendToWithErrno = (%d, %s), want (-1, %s)", ret, err, unix.ENETUNREACH) 259 } 260 } else { 261 if !(int(ret) == len(bytes) && err == unix.Errno(0)) { 262 t.Fatalf("got dut.SendToWithErrno = (%d, %s), want (%d, 0)", ret, err, len(bytes)) 263 } 264 } 265 266 // Verify the test runner received an ICMP packet with the correctly 267 // set "ident". 268 if env.ident != 0 { 269 icmpLayer.Ident = &env.ident 270 } 271 want := append(env.layers, icmpLayer) 272 if got, ok := env.conn.ListenForFrame(t, want, time.Second); !ok && expectPacket { 273 t.Fatalf("did not receive expected frame matching %s\nGot frames: %s", want, got) 274 } else if ok && !expectPacket { 275 matchedFrame := got[len(got)-1] 276 t.Fatalf("got unexpected frame matching %s\nGot frame: %s", want, matchedFrame) 277 } 278 }) 279 } 280 } 281 282 func (test *icmpV4Test) Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) { 283 if bindTo.To4() == nil || isBroadcastOrMulticast(dut, bindTo) { 284 // ICMPv4 sockets cannot bind to IPv6, broadcast, or multicast 285 // addresses. 286 return 287 } 288 289 expectPacket := (bindTo.Equal(dut.Net.RemoteIPv4) || bindTo.Equal(net.IPv4zero)) && sendTo.Equal(dut.Net.RemoteIPv4) 290 291 // TODO(gvisor.dev/issue/5763): Remove this if statement once gVisor 292 // restricts ICMP sockets to receive only from unicast addresses. 293 if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && bindTo.Equal(net.IPv4zero) && isBroadcastOrMulticast(dut, sendTo) { 294 expectPacket = true 295 } 296 297 env := test.setup(t, dut, bindTo, sendTo, bindToDevice) 298 299 for name, payload := range map[string][]byte{ 300 "empty": nil, 301 "small": []byte("hello world"), 302 "random1k": testbench.GenerateRandomPayload(t, maxICMPv4PayloadSize), 303 } { 304 t.Run(name, func(t *testing.T) { 305 icmpLayer := &testbench.ICMPv4{ 306 Type: testbench.ICMPv4Type(header.ICMPv4EchoReply), 307 Payload: payload, 308 } 309 if env.ident != 0 { 310 icmpLayer.Ident = &env.ident 311 } 312 313 // Send an ICMPv4 packet from the test runner to the DUT. 314 frame := env.conn.CreateFrame(t, env.layers, icmpLayer) 315 env.conn.SendFrame(t, frame) 316 317 // Verify the behavior of the ICMP socket on the DUT. 318 if expectPacket { 319 payload, err := icmpLayer.ToBytes() 320 if err != nil { 321 t.Fatalf("icmpLayer.ToBytes() = %s", err) 322 } 323 324 // Receive one extra byte to assert the length of the 325 // packet received in the case where the packet contains 326 // more data than expected. 327 len := int32(len(payload)) + 1 328 got, want := dut.Recv(t, env.socketFD, len, 0), payload 329 if diff := cmp.Diff(want, got); diff != "" { 330 t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff) 331 } 332 } else { 333 // Expected receive error, set a short receive timeout. 334 dut.SetSockOptTimeval( 335 t, 336 env.socketFD, 337 unix.SOL_SOCKET, 338 unix.SO_RCVTIMEO, 339 &unix.Timeval{ 340 Sec: 1, 341 Usec: 0, 342 }, 343 ) 344 ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, env.socketFD, maxICMPv4PayloadSize, 0) 345 if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK { 346 t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno) 347 } 348 } 349 }) 350 } 351 } 352 353 type icmpV6TestEnv struct { 354 socketFD int32 355 ident uint16 356 conn testbench.IPv6Conn 357 layers testbench.Layers 358 } 359 360 // icmpV6Test and icmpV4Test look substantially similar at first look, but have 361 // enough subtle differences in setup and test expectations to discourage 362 // refactoring: 363 // - Different IP layers 364 // - Different testbench.Connections 365 // - Different UNIX domain and proto arguments 366 // - Different expectPacket and wantErrno for send and receive 367 type icmpV6Test struct{} 368 369 func (test *icmpV6Test) setup(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) icmpV6TestEnv { 370 t.Helper() 371 372 // Tell the DUT to create a socket. 373 var socketFD int32 374 var ident uint16 375 376 if bindTo != nil { 377 socketFD, ident = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_ICMPV6, bindTo) 378 } else { 379 // An unbound socket will auto-bind to IN6ADDR_ANY_INIT. 380 socketFD = dut.Socket(t, unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_ICMPV6) 381 } 382 t.Cleanup(func() { 383 dut.Close(t, socketFD) 384 }) 385 386 if bindToDevice { 387 dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName)) 388 } 389 390 // Create a socket on the test runner. 391 conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) 392 t.Cleanup(func() { 393 conn.Close(t) 394 }) 395 396 dstAddr := sendTo.To16() 397 return icmpV6TestEnv{ 398 socketFD: socketFD, 399 ident: ident, 400 conn: conn, 401 layers: testbench.Layers{ 402 expectedEthLayer(t, dut, socketFD, sendTo), 403 &testbench.IPv6{ 404 DstAddr: &dstAddr, 405 }, 406 }, 407 } 408 } 409 410 func (test *icmpV6Test) Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) { 411 if bindTo.To4() != nil || bindTo.IsMulticast() { 412 // ICMPv6 sockets cannot bind to IPv4 or multicast addresses. 413 return 414 } 415 416 expectPacket := sendTo.Equal(dut.Net.LocalIPv6) 417 wantErrno := unix.Errno(0) 418 419 if sendTo.To4() != nil { 420 wantErrno = unix.EINVAL 421 422 // TODO(gvisor.dev/issue/5966): Remove this if statement once ICMPv6 sockets 423 // return EINVAL after calling sendto with an IPv4 address. 424 if dut.Uname.IsGvisor() || dut.Uname.IsFuchsia() { 425 wantErrno = unix.ENETUNREACH 426 if !bindTo.Equal(dut.Net.RemoteIPv6) && (bindToDevice || isInTestSubnetV4(dut, sendTo)) { 427 wantErrno = unix.Errno(0) 428 } 429 } 430 } 431 432 env := test.setup(t, dut, bindTo, sendTo, bindToDevice) 433 434 for name, payload := range map[string][]byte{ 435 "empty": nil, 436 "small": []byte("hello world"), 437 "random1k": testbench.GenerateRandomPayload(t, maxICMPv6PayloadSize), 438 } { 439 t.Run(name, func(t *testing.T) { 440 icmpLayer := &testbench.ICMPv6{ 441 Type: testbench.ICMPv6Type(header.ICMPv6EchoRequest), 442 Payload: payload, 443 } 444 bytes, err := icmpLayer.ToBytes() 445 if err != nil { 446 t.Fatalf("icmpLayer.ToBytes() = %s", err) 447 } 448 destSockaddr := unix.SockaddrInet6{ 449 ZoneId: dut.Net.RemoteDevID, 450 } 451 copy(destSockaddr.Addr[:], sendTo.To16()) 452 453 // Tell the DUT to send a packet out the ICMPv6 socket. 454 gotRet, gotErrno := dut.SendToWithErrno(context.Background(), t, env.socketFD, bytes, 0, &destSockaddr) 455 456 if gotErrno != wantErrno { 457 t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (_, %s), want = (_, %s)", env.socketFD, sendTo, gotErrno, wantErrno) 458 } 459 if wantErrno != 0 { 460 return 461 } 462 if got, want := int(gotRet), len(bytes); got != want { 463 t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (%d, _), want = (%d, _)", env.socketFD, sendTo, got, want) 464 } 465 466 // Verify the test runner received an ICMPv6 packet with the 467 // correctly set "ident". 468 if env.ident != 0 { 469 icmpLayer.Ident = &env.ident 470 } 471 want := append(env.layers, icmpLayer) 472 if got, ok := env.conn.ListenForFrame(t, want, time.Second); !ok && expectPacket { 473 t.Fatalf("did not receive expected frame matching %s\nGot frames: %s", want, got) 474 } else if ok && !expectPacket { 475 matchedFrame := got[len(got)-1] 476 t.Fatalf("got unexpected frame matching %s\nGot frame: %s", want, matchedFrame) 477 } 478 }) 479 } 480 } 481 482 func (test *icmpV6Test) Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) { 483 if bindTo.To4() != nil || bindTo.IsMulticast() { 484 // ICMPv6 sockets cannot bind to IPv4 or multicast addresses. 485 return 486 } 487 488 expectPacket := true 489 switch { 490 case bindTo.Equal(dut.Net.RemoteIPv6) && sendTo.Equal(dut.Net.RemoteIPv6): 491 case bindTo.Equal(net.IPv6zero) && sendTo.Equal(dut.Net.RemoteIPv6): 492 case bindTo.Equal(net.IPv6zero) && sendTo.Equal(net.IPv6linklocalallnodes): 493 default: 494 expectPacket = false 495 } 496 497 // TODO(gvisor.dev/issue/5763): Remove this if statement once gVisor 498 // restricts ICMP sockets to receive only from unicast addresses. 499 if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && bindTo.Equal(net.IPv6zero) && isBroadcastOrMulticast(dut, sendTo) { 500 expectPacket = false 501 } 502 503 env := test.setup(t, dut, bindTo, sendTo, bindToDevice) 504 505 for name, payload := range map[string][]byte{ 506 "empty": nil, 507 "small": []byte("hello world"), 508 "random1k": testbench.GenerateRandomPayload(t, maxICMPv6PayloadSize), 509 } { 510 t.Run(name, func(t *testing.T) { 511 icmpLayer := &testbench.ICMPv6{ 512 Type: testbench.ICMPv6Type(header.ICMPv6EchoReply), 513 Payload: payload, 514 } 515 if env.ident != 0 { 516 icmpLayer.Ident = &env.ident 517 } 518 519 // Send an ICMPv6 packet from the test runner to the DUT. 520 frame := env.conn.CreateFrame(t, env.layers, icmpLayer) 521 env.conn.SendFrame(t, frame) 522 523 // Verify the behavior of the ICMPv6 socket on the DUT. 524 if expectPacket { 525 payload, err := icmpLayer.ToBytes() 526 if err != nil { 527 t.Fatalf("icmpLayer.ToBytes() = %s", err) 528 } 529 530 // Receive one extra byte to assert the length of the 531 // packet received in the case where the packet contains 532 // more data than expected. 533 len := int32(len(payload)) + 1 534 got, want := dut.Recv(t, env.socketFD, len, 0), payload 535 if diff := cmp.Diff(want, got); diff != "" { 536 t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff) 537 } 538 } else { 539 // Expected receive error, set a short receive timeout. 540 dut.SetSockOptTimeval( 541 t, 542 env.socketFD, 543 unix.SOL_SOCKET, 544 unix.SO_RCVTIMEO, 545 &unix.Timeval{ 546 Sec: 1, 547 Usec: 0, 548 }, 549 ) 550 ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, env.socketFD, maxICMPv6PayloadSize, 0) 551 if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK { 552 t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno) 553 } 554 } 555 }) 556 } 557 } 558 559 type udpConn interface { 560 SrcPort(*testing.T) uint16 561 SendFrame(*testing.T, testbench.Layers, ...testbench.Layer) 562 ListenForFrame(*testing.T, testbench.Layers, time.Duration) ([]testbench.Layers, bool) 563 Close(*testing.T) 564 } 565 566 type udpTestEnv struct { 567 socketFD int32 568 conn udpConn 569 layers testbench.Layers 570 } 571 572 type udpTest struct{} 573 574 func (test *udpTest) setup(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) udpTestEnv { 575 t.Helper() 576 577 var ( 578 socketFD int32 579 outgoingUDP, incomingUDP testbench.UDP 580 ) 581 582 // Tell the DUT to create a socket. 583 if bindTo != nil { 584 var remotePort uint16 585 socketFD, remotePort = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, bindTo) 586 outgoingUDP.DstPort = &remotePort 587 incomingUDP.SrcPort = &remotePort 588 } else { 589 // An unbound socket will auto-bind to INADDR_ANY. 590 socketFD = dut.Socket(t, unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP) 591 } 592 t.Cleanup(func() { 593 dut.Close(t, socketFD) 594 }) 595 596 if bindToDevice { 597 dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName)) 598 } 599 600 // Create a socket on the test runner. 601 var conn udpConn 602 var ipLayer testbench.Layer 603 if addr := sendTo.To4(); addr != nil { 604 udpConn := dut.Net.NewUDPIPv4(t, outgoingUDP, incomingUDP) 605 conn = &udpConn 606 ipLayer = &testbench.IPv4{ 607 DstAddr: testbench.Address(tcpip.AddrFrom4Slice(addr)), 608 } 609 } else { 610 udpConn := dut.Net.NewUDPIPv6(t, outgoingUDP, incomingUDP) 611 conn = &udpConn 612 ipLayer = &testbench.IPv6{ 613 DstAddr: testbench.Address(tcpip.AddrFrom16Slice(sendTo.To16())), 614 } 615 } 616 t.Cleanup(func() { 617 conn.Close(t) 618 }) 619 620 return udpTestEnv{ 621 socketFD: socketFD, 622 conn: conn, 623 layers: testbench.Layers{ 624 expectedEthLayer(t, dut, socketFD, sendTo), 625 ipLayer, 626 &incomingUDP, 627 }, 628 } 629 } 630 631 func (test *udpTest) Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) { 632 wantErrno := unix.Errno(0) 633 634 if sendTo.To4() == nil { 635 // If sendTo is an IPv6 address. 636 if bindTo.To4() != nil { 637 // But bindTo is an IPv4 address, we expect EAFNOSUPPORT. 638 wantErrno = unix.EAFNOSUPPORT 639 640 // TODO(gvisor.dev/issue/5967): Remove this if statement once UDPv4 sockets 641 // returns EAFNOSUPPORT after calling sendto with an IPv6 address. 642 if dut.Uname.IsGvisor() { 643 wantErrno = unix.EINVAL 644 } 645 } 646 } else { 647 // If sendTo is an IPv4 address. 648 if bindTo.Equal(dut.Net.RemoteIPv6) { 649 // if bindTo is dut's IPv6 address, we expect ENETUNREACH. 650 wantErrno = unix.ENETUNREACH 651 } 652 653 if !bindToDevice && !bindTo.Equal(dut.Net.RemoteIPv4) && (sendTo.Equal(net.IPv4bcast) || sendTo.Equal(net.IPv4allsys)) { 654 // if not binding to a device, bindTo is not dut's IPv4 addression and sendTo is 655 // 255.255.255.255 or 224.0.0.1, we expect ENETUNERACH. 656 wantErrno = unix.ENETUNREACH 657 } 658 } 659 660 expectPacket := true 661 // We don't expect an incoming packet if: 662 // 1. sendTo is dut itself. 663 if isRemoteAddr(dut, sendTo) { 664 expectPacket = false 665 } 666 // 2. we expect an error when sending the packet. 667 if wantErrno != unix.Errno(0) { 668 expectPacket = false 669 } 670 671 env := test.setup(t, dut, bindTo, sendTo, bindToDevice) 672 673 for name, payload := range map[string][]byte{ 674 "empty": nil, 675 "small": []byte("hello world"), 676 "random1k": testbench.GenerateRandomPayload(t, maxUDPPayloadSize(bindTo)), 677 } { 678 t.Run(name, func(t *testing.T) { 679 var destSockaddr unix.Sockaddr 680 if sendTo4 := sendTo.To4(); sendTo4 != nil { 681 addr := unix.SockaddrInet4{ 682 Port: int(env.conn.SrcPort(t)), 683 } 684 copy(addr.Addr[:], sendTo4) 685 destSockaddr = &addr 686 } else { 687 addr := unix.SockaddrInet6{ 688 Port: int(env.conn.SrcPort(t)), 689 ZoneId: dut.Net.RemoteDevID, 690 } 691 copy(addr.Addr[:], sendTo.To16()) 692 destSockaddr = &addr 693 } 694 695 // Tell the DUT to send a packet out the UDP socket. 696 gotRet, gotErrno := dut.SendToWithErrno(context.Background(), t, env.socketFD, payload, 0, destSockaddr) 697 698 if gotErrno != wantErrno { 699 t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (_, %s), want = (_, %s)", env.socketFD, sendTo, gotErrno, wantErrno) 700 } 701 if wantErrno != 0 { 702 return 703 } 704 if got, want := int(gotRet), len(payload); got != want { 705 t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (%d, _), want = (%d, _)", env.socketFD, sendTo, got, want) 706 } 707 708 // Verify the test runner received a UDP packet with the 709 // correct payload. 710 want := append(env.layers, &testbench.Payload{ 711 Bytes: payload, 712 }) 713 if got, ok := env.conn.ListenForFrame(t, want, time.Second); !ok && expectPacket { 714 t.Fatalf("did not receive expected frame matching %s\nGot frames: %s", want, got) 715 } else if ok && !expectPacket { 716 matchedFrame := got[len(got)-1] 717 t.Fatalf("got unexpected frame matching %s\nGot frame: %s", want, matchedFrame) 718 } 719 }) 720 } 721 } 722 723 func (test *udpTest) Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) { 724 subnetBroadcast := dut.Net.SubnetBroadcast() 725 726 expectPacket := true 727 switch { 728 case bindTo.Equal(sendTo): 729 case bindTo.Equal(net.IPv4zero) && sameIPVersion(bindTo, sendTo) && !sendTo.Equal(dut.Net.LocalIPv4): 730 case bindTo.Equal(net.IPv6zero) && isBroadcast(dut, sendTo): 731 case bindTo.Equal(net.IPv6zero) && isRemoteAddr(dut, sendTo): 732 case bindTo.Equal(subnetBroadcast) && sendTo.Equal(subnetBroadcast): 733 default: 734 expectPacket = false 735 } 736 737 // TODO(gvisor.dev/issue/5956): Remove this if statement once gVisor 738 // restricts ICMP sockets to receive only from unicast addresses. 739 if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && bindTo.Equal(net.IPv6zero) && sendTo.Equal(net.IPv4allsys) { 740 expectPacket = true 741 } 742 743 env := test.setup(t, dut, bindTo, sendTo, bindToDevice) 744 maxPayloadSize := maxUDPPayloadSize(bindTo) 745 746 for name, payload := range map[string][]byte{ 747 "empty": nil, 748 "small": []byte("hello world"), 749 "random1k": testbench.GenerateRandomPayload(t, maxPayloadSize), 750 } { 751 t.Run(name, func(t *testing.T) { 752 // Send a UDP packet from the test runner to the DUT. 753 env.conn.SendFrame(t, env.layers, &testbench.Payload{Bytes: payload}) 754 755 // Verify the behavior of the ICMP socket on the DUT. 756 if expectPacket { 757 // Receive one extra byte to assert the length of the 758 // packet received in the case where the packet contains 759 // more data than expected. 760 len := int32(len(payload)) + 1 761 got, want := dut.Recv(t, env.socketFD, len, 0), payload 762 if diff := cmp.Diff(want, got); diff != "" { 763 t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff) 764 } 765 } else { 766 // Expected receive error, set a short receive timeout. 767 dut.SetSockOptTimeval( 768 t, 769 env.socketFD, 770 unix.SOL_SOCKET, 771 unix.SO_RCVTIMEO, 772 &unix.Timeval{ 773 Sec: 1, 774 Usec: 0, 775 }, 776 ) 777 ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, env.socketFD, int32(maxPayloadSize), 0) 778 if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK { 779 t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno) 780 } 781 } 782 }) 783 } 784 } 785 786 func isBroadcast(dut testbench.DUT, ip net.IP) bool { 787 return ip.Equal(net.IPv4bcast) || ip.Equal(dut.Net.SubnetBroadcast()) 788 } 789 790 func isBroadcastOrMulticast(dut testbench.DUT, ip net.IP) bool { 791 return isBroadcast(dut, ip) || ip.IsMulticast() 792 } 793 794 func sameIPVersion(a, b net.IP) bool { 795 return (a.To4() == nil) == (b.To4() == nil) 796 } 797 798 func isRemoteAddr(dut testbench.DUT, ip net.IP) bool { 799 return ip.Equal(dut.Net.RemoteIPv4) || ip.Equal(dut.Net.RemoteIPv6) 800 } 801 802 func isInTestSubnetV4(dut testbench.DUT, ip net.IP) bool { 803 network := net.IPNet{ 804 IP: dut.Net.LocalIPv4, 805 Mask: net.CIDRMask(dut.Net.IPv4PrefixLength, net.IPv4len*8), 806 } 807 return network.Contains(ip) 808 }