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