Files
gvisor/pkg/tcpip/tests/integration/multicast_broadcast_test.go
Kevin KrakauerandgVisor bot 3c75945fd6 netstack: remove PacketBuffer.IsNil()
The change was originally motivated by ticket references (cl/450976957), which
were never implemented.

PiperOrigin-RevId: 617480960
2024-03-20 05:31:48 -07:00

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)
}
}