mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
The change was originally motivated by ticket references (cl/450976957), which were never implemented. PiperOrigin-RevId: 617480960
833 lines
26 KiB
Go
833 lines
26 KiB
Go
// Copyright 2020 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package multicast_broadcast_test
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"gvisor.dev/gvisor/pkg/buffer"
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
|
"gvisor.dev/gvisor/pkg/tcpip/checker"
|
|
"gvisor.dev/gvisor/pkg/tcpip/checksum"
|
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
|
"gvisor.dev/gvisor/pkg/tcpip/link/loopback"
|
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
|
"gvisor.dev/gvisor/pkg/tcpip/prependable"
|
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
"gvisor.dev/gvisor/pkg/tcpip/tests/utils"
|
|
"gvisor.dev/gvisor/pkg/tcpip/testutil"
|
|
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
|
|
"gvisor.dev/gvisor/pkg/tcpip/transport/raw"
|
|
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
|
"gvisor.dev/gvisor/pkg/waiter"
|
|
)
|
|
|
|
const (
|
|
defaultMTU = 1280
|
|
ttl = 255
|
|
)
|
|
|
|
// TestPingMulticastBroadcast tests that responding to an Echo Request destined
|
|
// to a multicast or broadcast address uses a unicast source address for the
|
|
// reply.
|
|
func TestPingMulticastBroadcast(t *testing.T) {
|
|
const (
|
|
nicID = 1
|
|
ttl = 64
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
protoNum tcpip.NetworkProtocolNumber
|
|
rxICMP func(*channel.Endpoint, tcpip.Address, tcpip.Address, uint8)
|
|
srcAddr tcpip.Address
|
|
dstAddr tcpip.Address
|
|
expectedSrc tcpip.Address
|
|
}{
|
|
{
|
|
name: "IPv4 unicast",
|
|
protoNum: header.IPv4ProtocolNumber,
|
|
dstAddr: utils.Ipv4Addr.Address,
|
|
srcAddr: utils.RemoteIPv4Addr,
|
|
rxICMP: utils.RxICMPv4EchoRequest,
|
|
expectedSrc: utils.Ipv4Addr.Address,
|
|
},
|
|
{
|
|
name: "IPv4 directed broadcast",
|
|
protoNum: header.IPv4ProtocolNumber,
|
|
rxICMP: utils.RxICMPv4EchoRequest,
|
|
srcAddr: utils.RemoteIPv4Addr,
|
|
dstAddr: utils.Ipv4SubnetBcast,
|
|
expectedSrc: utils.Ipv4Addr.Address,
|
|
},
|
|
{
|
|
name: "IPv4 broadcast",
|
|
protoNum: header.IPv4ProtocolNumber,
|
|
rxICMP: utils.RxICMPv4EchoRequest,
|
|
srcAddr: utils.RemoteIPv4Addr,
|
|
dstAddr: header.IPv4Broadcast,
|
|
expectedSrc: utils.Ipv4Addr.Address,
|
|
},
|
|
{
|
|
name: "IPv4 all-systems multicast",
|
|
protoNum: header.IPv4ProtocolNumber,
|
|
rxICMP: utils.RxICMPv4EchoRequest,
|
|
srcAddr: utils.RemoteIPv4Addr,
|
|
dstAddr: header.IPv4AllSystems,
|
|
expectedSrc: utils.Ipv4Addr.Address,
|
|
},
|
|
{
|
|
name: "IPv6 unicast",
|
|
protoNum: header.IPv6ProtocolNumber,
|
|
rxICMP: utils.RxICMPv6EchoRequest,
|
|
srcAddr: utils.RemoteIPv6Addr,
|
|
dstAddr: utils.Ipv6Addr.Address,
|
|
expectedSrc: utils.Ipv6Addr.Address,
|
|
},
|
|
{
|
|
name: "IPv6 all-nodes multicast",
|
|
protoNum: header.IPv6ProtocolNumber,
|
|
rxICMP: utils.RxICMPv6EchoRequest,
|
|
srcAddr: utils.RemoteIPv6Addr,
|
|
dstAddr: header.IPv6AllNodesMulticastAddress,
|
|
expectedSrc: utils.Ipv6Addr.Address,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
|
TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol4, icmp.NewProtocol6},
|
|
})
|
|
// We only expect a single packet in response to our ICMP Echo Request.
|
|
e := channel.New(1, defaultMTU, "")
|
|
defer e.Close()
|
|
if err := s.CreateNIC(nicID, e); err != nil {
|
|
t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
|
|
}
|
|
ipv4ProtoAddr := tcpip.ProtocolAddress{Protocol: header.IPv4ProtocolNumber, AddressWithPrefix: utils.Ipv4Addr}
|
|
if err := s.AddProtocolAddress(nicID, ipv4ProtoAddr, stack.AddressProperties{}); err != nil {
|
|
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, ipv4ProtoAddr, err)
|
|
}
|
|
ipv6ProtoAddr := tcpip.ProtocolAddress{Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: utils.Ipv6Addr}
|
|
if err := s.AddProtocolAddress(nicID, ipv6ProtoAddr, stack.AddressProperties{}); err != nil {
|
|
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, ipv6ProtoAddr, err)
|
|
}
|
|
|
|
// Default routes for IPv4 and IPv6 so ICMP can find a route to the remote
|
|
// node when attempting to send the ICMP Echo Reply.
|
|
s.SetRouteTable([]tcpip.Route{
|
|
{
|
|
Destination: header.IPv6EmptySubnet,
|
|
NIC: nicID,
|
|
},
|
|
{
|
|
Destination: header.IPv4EmptySubnet,
|
|
NIC: nicID,
|
|
},
|
|
})
|
|
|
|
test.rxICMP(e, test.srcAddr, test.dstAddr, ttl)
|
|
pkt := e.Read()
|
|
if pkt == nil {
|
|
t.Fatal("expected ICMP response")
|
|
}
|
|
defer pkt.DecRef()
|
|
|
|
if pkt.EgressRoute.LocalAddress != test.expectedSrc {
|
|
t.Errorf("got pkt.EgressRoute.LocalAddress = %s, want = %s", pkt.EgressRoute.LocalAddress, test.expectedSrc)
|
|
}
|
|
// The destination of the response packet should be the source of the
|
|
// original packet.
|
|
if pkt.EgressRoute.RemoteAddress != test.srcAddr {
|
|
t.Errorf("got pkt.EgressRoute.RemoteAddress = %s, want = %s", pkt.EgressRoute.RemoteAddress, test.srcAddr)
|
|
}
|
|
|
|
v := stack.PayloadSince(pkt.NetworkHeader())
|
|
defer v.Release()
|
|
src, dst := s.NetworkProtocolInstance(test.protoNum).ParseAddresses(v.AsSlice())
|
|
if src != test.expectedSrc {
|
|
t.Errorf("got pkt source = %s, want = %s", src, test.expectedSrc)
|
|
}
|
|
// The destination of the response packet should be the source of the
|
|
// original packet.
|
|
if dst != test.srcAddr {
|
|
t.Errorf("got pkt destination = %s, want = %s", dst, test.srcAddr)
|
|
}
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func rxIPv4UDP(e *channel.Endpoint, src, dst tcpip.Address, data []byte) {
|
|
payloadLen := header.UDPMinimumSize + len(data)
|
|
totalLen := header.IPv4MinimumSize + payloadLen
|
|
hdr := prependable.New(totalLen)
|
|
u := header.UDP(hdr.Prepend(payloadLen))
|
|
u.Encode(&header.UDPFields{
|
|
SrcPort: utils.RemotePort,
|
|
DstPort: utils.LocalPort,
|
|
Length: uint16(payloadLen),
|
|
})
|
|
copy(u.Payload(), data)
|
|
sum := header.PseudoHeaderChecksum(udp.ProtocolNumber, src, dst, uint16(payloadLen))
|
|
sum = checksum.Checksum(data, sum)
|
|
u.SetChecksum(^u.CalculateChecksum(sum))
|
|
|
|
ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize))
|
|
ip.Encode(&header.IPv4Fields{
|
|
TotalLength: uint16(totalLen),
|
|
Protocol: uint8(udp.ProtocolNumber),
|
|
TTL: ttl,
|
|
SrcAddr: src,
|
|
DstAddr: dst,
|
|
})
|
|
ip.SetChecksum(^ip.CalculateChecksum())
|
|
|
|
e.InjectInbound(header.IPv4ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
Payload: buffer.MakeWithData(hdr.View()),
|
|
}))
|
|
}
|
|
|
|
func rxIPv6UDP(e *channel.Endpoint, src, dst tcpip.Address, data []byte) {
|
|
payloadLen := header.UDPMinimumSize + len(data)
|
|
hdr := prependable.New(header.IPv6MinimumSize + payloadLen)
|
|
u := header.UDP(hdr.Prepend(payloadLen))
|
|
u.Encode(&header.UDPFields{
|
|
SrcPort: utils.RemotePort,
|
|
DstPort: utils.LocalPort,
|
|
Length: uint16(payloadLen),
|
|
})
|
|
copy(u.Payload(), data)
|
|
sum := header.PseudoHeaderChecksum(udp.ProtocolNumber, src, dst, uint16(payloadLen))
|
|
sum = checksum.Checksum(data, sum)
|
|
u.SetChecksum(^u.CalculateChecksum(sum))
|
|
|
|
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
|
|
ip.Encode(&header.IPv6Fields{
|
|
PayloadLength: uint16(payloadLen),
|
|
TransportProtocol: udp.ProtocolNumber,
|
|
HopLimit: ttl,
|
|
SrcAddr: src,
|
|
DstAddr: dst,
|
|
})
|
|
|
|
e.InjectInbound(header.IPv6ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
Payload: buffer.MakeWithData(hdr.View()),
|
|
}))
|
|
}
|
|
|
|
// TestIncomingMulticastAndBroadcast tests receiving a packet destined to some
|
|
// multicast or broadcast address.
|
|
func TestIncomingMulticastAndBroadcast(t *testing.T) {
|
|
const nicID = 1
|
|
|
|
data := []byte{1, 2, 3, 4}
|
|
|
|
tests := []struct {
|
|
name string
|
|
proto tcpip.NetworkProtocolNumber
|
|
remoteAddr tcpip.Address
|
|
localAddr tcpip.AddressWithPrefix
|
|
rxUDP func(*channel.Endpoint, tcpip.Address, tcpip.Address, []byte)
|
|
bindAddr tcpip.Address
|
|
dstAddr tcpip.Address
|
|
expectRx bool
|
|
}{
|
|
{
|
|
name: "IPv4 unicast binding to unicast",
|
|
proto: header.IPv4ProtocolNumber,
|
|
remoteAddr: utils.RemoteIPv4Addr,
|
|
localAddr: utils.Ipv4Addr,
|
|
rxUDP: rxIPv4UDP,
|
|
bindAddr: utils.Ipv4Addr.Address,
|
|
dstAddr: utils.Ipv4Addr.Address,
|
|
expectRx: true,
|
|
},
|
|
{
|
|
name: "IPv4 unicast binding to broadcast",
|
|
proto: header.IPv4ProtocolNumber,
|
|
remoteAddr: utils.RemoteIPv4Addr,
|
|
localAddr: utils.Ipv4Addr,
|
|
rxUDP: rxIPv4UDP,
|
|
bindAddr: header.IPv4Broadcast,
|
|
dstAddr: utils.Ipv4Addr.Address,
|
|
expectRx: false,
|
|
},
|
|
{
|
|
name: "IPv4 unicast binding to wildcard",
|
|
proto: header.IPv4ProtocolNumber,
|
|
remoteAddr: utils.RemoteIPv4Addr,
|
|
localAddr: utils.Ipv4Addr,
|
|
rxUDP: rxIPv4UDP,
|
|
dstAddr: utils.Ipv4Addr.Address,
|
|
expectRx: true,
|
|
},
|
|
|
|
{
|
|
name: "IPv4 directed broadcast binding to subnet broadcast",
|
|
proto: header.IPv4ProtocolNumber,
|
|
remoteAddr: utils.RemoteIPv4Addr,
|
|
localAddr: utils.Ipv4Addr,
|
|
rxUDP: rxIPv4UDP,
|
|
bindAddr: utils.Ipv4SubnetBcast,
|
|
dstAddr: utils.Ipv4SubnetBcast,
|
|
expectRx: true,
|
|
},
|
|
{
|
|
name: "IPv4 directed broadcast binding to broadcast",
|
|
proto: header.IPv4ProtocolNumber,
|
|
remoteAddr: utils.RemoteIPv4Addr,
|
|
localAddr: utils.Ipv4Addr,
|
|
rxUDP: rxIPv4UDP,
|
|
bindAddr: header.IPv4Broadcast,
|
|
dstAddr: utils.Ipv4SubnetBcast,
|
|
expectRx: false,
|
|
},
|
|
{
|
|
name: "IPv4 directed broadcast binding to wildcard",
|
|
proto: header.IPv4ProtocolNumber,
|
|
remoteAddr: utils.RemoteIPv4Addr,
|
|
localAddr: utils.Ipv4Addr,
|
|
rxUDP: rxIPv4UDP,
|
|
dstAddr: utils.Ipv4SubnetBcast,
|
|
expectRx: true,
|
|
},
|
|
|
|
{
|
|
name: "IPv4 broadcast binding to broadcast",
|
|
proto: header.IPv4ProtocolNumber,
|
|
remoteAddr: utils.RemoteIPv4Addr,
|
|
localAddr: utils.Ipv4Addr,
|
|
rxUDP: rxIPv4UDP,
|
|
bindAddr: header.IPv4Broadcast,
|
|
dstAddr: header.IPv4Broadcast,
|
|
expectRx: true,
|
|
},
|
|
{
|
|
name: "IPv4 broadcast binding to subnet broadcast",
|
|
proto: header.IPv4ProtocolNumber,
|
|
remoteAddr: utils.RemoteIPv4Addr,
|
|
localAddr: utils.Ipv4Addr,
|
|
rxUDP: rxIPv4UDP,
|
|
bindAddr: utils.Ipv4SubnetBcast,
|
|
dstAddr: header.IPv4Broadcast,
|
|
expectRx: false,
|
|
},
|
|
{
|
|
name: "IPv4 broadcast binding to wildcard",
|
|
proto: header.IPv4ProtocolNumber,
|
|
remoteAddr: utils.RemoteIPv4Addr,
|
|
localAddr: utils.Ipv4Addr,
|
|
rxUDP: rxIPv4UDP,
|
|
dstAddr: utils.Ipv4SubnetBcast,
|
|
expectRx: true,
|
|
},
|
|
|
|
{
|
|
name: "IPv4 all-systems multicast binding to all-systems multicast",
|
|
proto: header.IPv4ProtocolNumber,
|
|
remoteAddr: utils.RemoteIPv4Addr,
|
|
localAddr: utils.Ipv4Addr,
|
|
rxUDP: rxIPv4UDP,
|
|
bindAddr: header.IPv4AllSystems,
|
|
dstAddr: header.IPv4AllSystems,
|
|
expectRx: true,
|
|
},
|
|
{
|
|
name: "IPv4 all-systems multicast binding to wildcard",
|
|
proto: header.IPv4ProtocolNumber,
|
|
remoteAddr: utils.RemoteIPv4Addr,
|
|
localAddr: utils.Ipv4Addr,
|
|
rxUDP: rxIPv4UDP,
|
|
dstAddr: header.IPv4AllSystems,
|
|
expectRx: true,
|
|
},
|
|
{
|
|
name: "IPv4 all-systems multicast binding to unicast",
|
|
proto: header.IPv4ProtocolNumber,
|
|
remoteAddr: utils.RemoteIPv4Addr,
|
|
localAddr: utils.Ipv4Addr,
|
|
rxUDP: rxIPv4UDP,
|
|
bindAddr: utils.Ipv4Addr.Address,
|
|
dstAddr: header.IPv4AllSystems,
|
|
expectRx: false,
|
|
},
|
|
|
|
// IPv6 has no notion of a broadcast.
|
|
{
|
|
name: "IPv6 unicast binding to wildcard",
|
|
dstAddr: utils.Ipv6Addr.Address,
|
|
proto: header.IPv6ProtocolNumber,
|
|
remoteAddr: utils.RemoteIPv6Addr,
|
|
localAddr: utils.Ipv6Addr,
|
|
rxUDP: rxIPv6UDP,
|
|
expectRx: true,
|
|
},
|
|
{
|
|
name: "IPv6 broadcast-like address binding to wildcard",
|
|
dstAddr: utils.Ipv6SubnetBcast,
|
|
proto: header.IPv6ProtocolNumber,
|
|
remoteAddr: utils.RemoteIPv6Addr,
|
|
localAddr: utils.Ipv6Addr,
|
|
rxUDP: rxIPv6UDP,
|
|
expectRx: false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
|
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
|
|
})
|
|
e := channel.New(0, defaultMTU, "")
|
|
defer e.Close()
|
|
if err := s.CreateNIC(nicID, e); err != nil {
|
|
t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
|
|
}
|
|
protoAddr := tcpip.ProtocolAddress{Protocol: test.proto, AddressWithPrefix: test.localAddr}
|
|
if err := s.AddProtocolAddress(nicID, protoAddr, stack.AddressProperties{}); err != nil {
|
|
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protoAddr, err)
|
|
}
|
|
|
|
var wq waiter.Queue
|
|
ep, err := s.NewEndpoint(udp.ProtocolNumber, test.proto, &wq)
|
|
if err != nil {
|
|
t.Fatalf("NewEndpoint(%d, %d, _): %s", udp.ProtocolNumber, test.proto, err)
|
|
}
|
|
defer ep.Close()
|
|
|
|
bindAddr := tcpip.FullAddress{Addr: test.bindAddr, Port: utils.LocalPort}
|
|
if err := ep.Bind(bindAddr); err != nil {
|
|
t.Fatalf("ep.Bind(%#v): %s", bindAddr, err)
|
|
}
|
|
|
|
test.rxUDP(e, test.remoteAddr, test.dstAddr, data)
|
|
var buf bytes.Buffer
|
|
var opts tcpip.ReadOptions
|
|
if res, err := ep.Read(&buf, opts); test.expectRx {
|
|
if err != nil {
|
|
t.Fatalf("ep.Read(_, %#v): %s", opts, err)
|
|
}
|
|
if diff := cmp.Diff(tcpip.ReadResult{
|
|
Count: buf.Len(),
|
|
Total: buf.Len(),
|
|
}, res, checker.IgnoreCmpPath("ControlMessages")); diff != "" {
|
|
t.Errorf("ep.Read: unexpected result (-want +got):\n%s", diff)
|
|
}
|
|
if diff := cmp.Diff(data, buf.Bytes()); diff != "" {
|
|
t.Errorf("got UDP payload mismatch (-want +got):\n%s", diff)
|
|
}
|
|
} else if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
|
|
t.Fatalf("got Read = (%v, %s) [with data %x], want = (_, %s)", res, err, buf.Bytes(), &tcpip.ErrWouldBlock{})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestReuseAddrAndBroadcast makes sure broadcast packets are received by all
|
|
// interested endpoints.
|
|
func TestReuseAddrAndBroadcast(t *testing.T) {
|
|
const (
|
|
nicID = 1
|
|
localPort = 9000
|
|
)
|
|
loopbackBroadcast := testutil.MustParse4("127.255.255.255")
|
|
|
|
tests := []struct {
|
|
name string
|
|
broadcastAddr tcpip.Address
|
|
}{
|
|
{
|
|
name: "Subnet directed broadcast",
|
|
broadcastAddr: loopbackBroadcast,
|
|
},
|
|
{
|
|
name: "IPv4 broadcast",
|
|
broadcastAddr: header.IPv4Broadcast,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
|
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
|
|
})
|
|
if err := s.CreateNIC(nicID, loopback.New()); err != nil {
|
|
t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
|
|
}
|
|
protoAddr := tcpip.ProtocolAddress{
|
|
Protocol: header.IPv4ProtocolNumber,
|
|
AddressWithPrefix: tcpip.AddressWithPrefix{
|
|
Address: tcpip.AddrFromSlice([]byte("\x7f\x00\x00\x01")),
|
|
PrefixLen: 8,
|
|
},
|
|
}
|
|
if err := s.AddProtocolAddress(nicID, protoAddr, stack.AddressProperties{}); err != nil {
|
|
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protoAddr, err)
|
|
}
|
|
|
|
s.SetRouteTable([]tcpip.Route{
|
|
{
|
|
// We use the empty subnet instead of just the loopback subnet so we
|
|
// also have a route to the IPv4 Broadcast address.
|
|
Destination: header.IPv4EmptySubnet,
|
|
NIC: nicID,
|
|
},
|
|
})
|
|
|
|
type endpointAndWaiter struct {
|
|
ep tcpip.Endpoint
|
|
ch chan struct{}
|
|
}
|
|
var eps []endpointAndWaiter
|
|
// We create endpoints that bind to both the wildcard address and the
|
|
// broadcast address to make sure both of these types of "broadcast
|
|
// interested" endpoints receive broadcast packets.
|
|
for _, bindWildcard := range []bool{false, true} {
|
|
// Create multiple endpoints for each type of "broadcast interested"
|
|
// endpoint so we can test that all endpoints receive the broadcast
|
|
// packet.
|
|
for i := 0; i < 2; i++ {
|
|
var wq waiter.Queue
|
|
we, ch := waiter.NewChannelEntry(waiter.ReadableEvents)
|
|
wq.EventRegister(&we)
|
|
ep, err := s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
|
|
if err != nil {
|
|
t.Fatalf("(eps[%d]) NewEndpoint(%d, %d, _): %s", len(eps), udp.ProtocolNumber, ipv4.ProtocolNumber, err)
|
|
}
|
|
defer ep.Close()
|
|
|
|
ep.SocketOptions().SetReuseAddress(true)
|
|
ep.SocketOptions().SetBroadcast(true)
|
|
|
|
bindAddr := tcpip.FullAddress{Port: localPort}
|
|
if bindWildcard {
|
|
if err := ep.Bind(bindAddr); err != nil {
|
|
t.Fatalf("eps[%d].Bind(%#v): %s", len(eps), bindAddr, err)
|
|
}
|
|
} else {
|
|
bindAddr.Addr = test.broadcastAddr
|
|
if err := ep.Bind(bindAddr); err != nil {
|
|
t.Fatalf("eps[%d].Bind(%#v): %s", len(eps), bindAddr, err)
|
|
}
|
|
}
|
|
|
|
eps = append(eps, endpointAndWaiter{ep: ep, ch: ch})
|
|
}
|
|
}
|
|
|
|
for i, wep := range eps {
|
|
writeOpts := tcpip.WriteOptions{
|
|
To: &tcpip.FullAddress{
|
|
Addr: test.broadcastAddr,
|
|
Port: localPort,
|
|
},
|
|
}
|
|
data := []byte{byte(i), 2, 3, 4}
|
|
var r bytes.Reader
|
|
r.Reset(data)
|
|
if n, err := wep.ep.Write(&r, writeOpts); err != nil {
|
|
t.Fatalf("eps[%d].Write(_, _): %s", i, err)
|
|
} else if want := int64(len(data)); n != want {
|
|
t.Fatalf("got eps[%d].Write(_, _) = (%d, nil), want = (%d, nil)", i, n, want)
|
|
}
|
|
|
|
for j, rep := range eps {
|
|
// Wait for the endpoint to become readable.
|
|
<-rep.ch
|
|
|
|
var buf bytes.Buffer
|
|
result, err := rep.ep.Read(&buf, tcpip.ReadOptions{})
|
|
if err != nil {
|
|
t.Errorf("(eps[%d] write) eps[%d].Read: %s", i, j, err)
|
|
continue
|
|
}
|
|
if diff := cmp.Diff(tcpip.ReadResult{
|
|
Count: buf.Len(),
|
|
Total: buf.Len(),
|
|
}, result, checker.IgnoreCmpPath("ControlMessages")); diff != "" {
|
|
t.Errorf("(eps[%d] write) eps[%d].Read: unexpected result (-want +got):\n%s", i, j, diff)
|
|
}
|
|
if diff := cmp.Diff([]byte(data), buf.Bytes()); diff != "" {
|
|
t.Errorf("(eps[%d] write) got UDP payload from eps[%d] mismatch (-want +got):\n%s", i, j, diff)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUDPAddRemoveMembershipSocketOption(t *testing.T) {
|
|
const (
|
|
nicID = 1
|
|
)
|
|
|
|
data := []byte{1, 2, 3, 4}
|
|
|
|
tests := []struct {
|
|
name string
|
|
proto tcpip.NetworkProtocolNumber
|
|
remoteAddr tcpip.Address
|
|
localAddr tcpip.AddressWithPrefix
|
|
rxUDP func(*channel.Endpoint, tcpip.Address, tcpip.Address, []byte)
|
|
multicastAddr tcpip.Address
|
|
}{
|
|
{
|
|
name: "IPv4 unicast binding to unicast",
|
|
multicastAddr: tcpip.AddrFromSlice([]byte("\xe0\x01\x02\x03")),
|
|
proto: header.IPv4ProtocolNumber,
|
|
remoteAddr: utils.RemoteIPv4Addr,
|
|
localAddr: utils.Ipv4Addr,
|
|
rxUDP: rxIPv4UDP,
|
|
},
|
|
{
|
|
name: "IPv6 broadcast-like address binding to wildcard",
|
|
multicastAddr: tcpip.AddrFromSlice([]byte("\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04")),
|
|
proto: header.IPv6ProtocolNumber,
|
|
remoteAddr: utils.RemoteIPv6Addr,
|
|
localAddr: utils.Ipv6Addr,
|
|
rxUDP: rxIPv6UDP,
|
|
},
|
|
}
|
|
|
|
subTests := []struct {
|
|
name string
|
|
specifyNICID bool
|
|
specifyNICAddr bool
|
|
}{
|
|
{
|
|
name: "Specify NIC ID and NIC address",
|
|
specifyNICID: true,
|
|
specifyNICAddr: true,
|
|
},
|
|
{
|
|
name: "Don't specify NIC ID or NIC address",
|
|
specifyNICID: false,
|
|
specifyNICAddr: false,
|
|
},
|
|
{
|
|
name: "Specify NIC ID but don't specify NIC address",
|
|
specifyNICID: true,
|
|
specifyNICAddr: false,
|
|
},
|
|
{
|
|
name: "Don't specify NIC ID but specify NIC address",
|
|
specifyNICID: false,
|
|
specifyNICAddr: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
for _, subTest := range subTests {
|
|
t.Run(subTest.name, func(t *testing.T) {
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
|
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
|
|
})
|
|
e := channel.New(0, defaultMTU, "")
|
|
defer e.Close()
|
|
if err := s.CreateNIC(nicID, e); err != nil {
|
|
t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
|
|
}
|
|
protoAddr := tcpip.ProtocolAddress{Protocol: test.proto, AddressWithPrefix: test.localAddr}
|
|
if err := s.AddProtocolAddress(nicID, protoAddr, stack.AddressProperties{}); err != nil {
|
|
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protoAddr, err)
|
|
}
|
|
|
|
// Set the route table so that UDP can find a NIC that is
|
|
// routable to the multicast address when the NIC isn't specified.
|
|
if !subTest.specifyNICID && !subTest.specifyNICAddr {
|
|
s.SetRouteTable([]tcpip.Route{
|
|
{
|
|
Destination: header.IPv6EmptySubnet,
|
|
NIC: nicID,
|
|
},
|
|
{
|
|
Destination: header.IPv4EmptySubnet,
|
|
NIC: nicID,
|
|
},
|
|
})
|
|
}
|
|
|
|
var wq waiter.Queue
|
|
ep, err := s.NewEndpoint(udp.ProtocolNumber, test.proto, &wq)
|
|
if err != nil {
|
|
t.Fatalf("NewEndpoint(%d, %d, _): %s", udp.ProtocolNumber, test.proto, err)
|
|
}
|
|
defer ep.Close()
|
|
|
|
bindAddr := tcpip.FullAddress{Port: utils.LocalPort}
|
|
if err := ep.Bind(bindAddr); err != nil {
|
|
t.Fatalf("ep.Bind(%#v): %s", bindAddr, err)
|
|
}
|
|
|
|
memOpt := tcpip.MembershipOption{MulticastAddr: test.multicastAddr}
|
|
if subTest.specifyNICID {
|
|
memOpt.NIC = nicID
|
|
}
|
|
if subTest.specifyNICAddr {
|
|
memOpt.InterfaceAddr = test.localAddr.Address
|
|
}
|
|
|
|
// We should receive UDP packets to the group once we join the
|
|
// multicast group.
|
|
addOpt := tcpip.AddMembershipOption(memOpt)
|
|
if err := ep.SetSockOpt(&addOpt); err != nil {
|
|
t.Fatalf("ep.SetSockOpt(&%#v): %s", addOpt, err)
|
|
}
|
|
test.rxUDP(e, test.remoteAddr, test.multicastAddr, data)
|
|
var buf bytes.Buffer
|
|
result, err := ep.Read(&buf, tcpip.ReadOptions{})
|
|
if err != nil {
|
|
t.Fatalf("ep.Read: %s", err)
|
|
} else {
|
|
if diff := cmp.Diff(tcpip.ReadResult{
|
|
Count: buf.Len(),
|
|
Total: buf.Len(),
|
|
}, result, checker.IgnoreCmpPath("ControlMessages")); diff != "" {
|
|
t.Errorf("ep.Read: unexpected result (-want +got):\n%s", diff)
|
|
}
|
|
if diff := cmp.Diff(data, buf.Bytes()); diff != "" {
|
|
t.Errorf("got UDP payload mismatch (-want +got):\n%s", diff)
|
|
}
|
|
}
|
|
|
|
// We should not receive UDP packets to the group once we leave
|
|
// the multicast group.
|
|
removeOpt := tcpip.RemoveMembershipOption(memOpt)
|
|
if err := ep.SetSockOpt(&removeOpt); err != nil {
|
|
t.Fatalf("ep.SetSockOpt(&%#v): %s", removeOpt, err)
|
|
}
|
|
{
|
|
_, err := ep.Read(&buf, tcpip.ReadOptions{})
|
|
if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
|
|
t.Fatalf("got ep.Read = (_, %s), want = (_, %s)", err, &tcpip.ErrWouldBlock{})
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAddMembershipInterfacePrecedence(t *testing.T) {
|
|
const nicID = 1
|
|
multicastAddr := tcpip.AddrFromSlice([]byte("\xe0\x01\x02\x03"))
|
|
proto := header.IPv4ProtocolNumber
|
|
// This address is nonsensical. If the precedence is correct, this should not
|
|
// matter, because ADD_IP_MEMBERSHIP should consider the interface index
|
|
// and use that before checking the address.
|
|
localAddr := tcpip.AddressWithPrefix{
|
|
Address: testutil.MustParse4("8.0.8.0"),
|
|
PrefixLen: 24,
|
|
}
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
|
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
|
|
})
|
|
e := channel.New(0, defaultMTU, "")
|
|
defer e.Close()
|
|
if err := s.CreateNIC(nicID, e); err != nil {
|
|
t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
|
|
}
|
|
protoAddr := tcpip.ProtocolAddress{Protocol: proto, AddressWithPrefix: localAddr}
|
|
if err := s.AddProtocolAddress(nicID, protoAddr, stack.AddressProperties{}); err != nil {
|
|
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protoAddr, err)
|
|
}
|
|
|
|
var wq waiter.Queue
|
|
ep, err := s.NewEndpoint(udp.ProtocolNumber, proto, &wq)
|
|
if err != nil {
|
|
t.Fatalf("NewEndpoint(%d, %d, _): %s", udp.ProtocolNumber, proto, err)
|
|
}
|
|
defer ep.Close()
|
|
|
|
bindAddr := tcpip.FullAddress{Port: utils.LocalPort}
|
|
if err := ep.Bind(bindAddr); err != nil {
|
|
t.Fatalf("ep.Bind(%#v): %s", bindAddr, err)
|
|
}
|
|
|
|
memOpt := tcpip.MembershipOption{MulticastAddr: multicastAddr}
|
|
memOpt.NIC = nicID
|
|
memOpt.InterfaceAddr = localAddr.Address
|
|
|
|
// Add membership should succeed when the interface index is specified,
|
|
// even if a bad interface address is specified.
|
|
addOpt := tcpip.AddMembershipOption(memOpt)
|
|
if err := ep.SetSockOpt(&addOpt); err != nil {
|
|
t.Fatalf("ep.SetSockOpt(&%#v): %s", addOpt, err)
|
|
}
|
|
}
|
|
|
|
func TestMismatchedMulticastAddressAndProtocol(t *testing.T) {
|
|
const nicID = 1
|
|
// MulticastAddr is IPv4, but proto is IPv6.
|
|
multicastAddr := tcpip.AddrFromSlice([]byte("\xe0\x01\x02\x03"))
|
|
s := stack.New(stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
|
TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol6},
|
|
RawFactory: raw.EndpointFactory{},
|
|
})
|
|
e := channel.New(0, defaultMTU, "")
|
|
defer e.Close()
|
|
if err := s.CreateNIC(nicID, e); err != nil {
|
|
t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
|
|
}
|
|
protoAddr := tcpip.ProtocolAddress{Protocol: header.IPv4ProtocolNumber, AddressWithPrefix: utils.Ipv4Addr}
|
|
if err := s.AddProtocolAddress(nicID, protoAddr, stack.AddressProperties{}); err != nil {
|
|
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protoAddr, err)
|
|
}
|
|
|
|
var wq waiter.Queue
|
|
ep, err := s.NewRawEndpoint(header.ICMPv6ProtocolNumber, header.IPv6ProtocolNumber, &wq, false)
|
|
if err != nil {
|
|
t.Fatalf("NewEndpoint(%d, %d, _): %s", udp.ProtocolNumber, header.IPv6ProtocolNumber, err)
|
|
}
|
|
defer ep.Close()
|
|
|
|
bindAddr := tcpip.FullAddress{Port: utils.LocalPort}
|
|
if err := ep.Bind(bindAddr); err != nil {
|
|
t.Fatalf("ep.Bind(%#v): %s", bindAddr, err)
|
|
}
|
|
|
|
memOpt := tcpip.MembershipOption{
|
|
MulticastAddr: multicastAddr,
|
|
NIC: 0,
|
|
InterfaceAddr: utils.Ipv4Addr.Address,
|
|
}
|
|
|
|
// Add/remove membership should succeed when the interface index is specified,
|
|
// even if a bad interface address is specified.
|
|
addOpt := tcpip.AddMembershipOption(memOpt)
|
|
expErr := &tcpip.ErrInvalidOptionValue{}
|
|
if err := ep.SetSockOpt(&addOpt); err != expErr {
|
|
t.Fatalf("ep.SetSockOpt(&%#v): want %q, got %q", addOpt, expErr, err)
|
|
}
|
|
|
|
removeOpt := tcpip.RemoveMembershipOption(memOpt)
|
|
if err := ep.SetSockOpt(&removeOpt); err != expErr {
|
|
t.Fatalf("ep.SetSockOpt(&%#v): want %q, got %q", addOpt, expErr, err)
|
|
}
|
|
}
|