mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Support listener-side MLDv1
...as defined by RFC 2710. Querier (router)-side MLDv1 is not yet supported. The core state machine is shared with IGMPv2. This is guarded behind a flag (ipv6.Options.MLDEnabled). Tests: ip_test.TestMGP* Bug #4861 PiperOrigin-RevId: 344344095
This commit is contained in:
committed by
gVisor bot
parent
2485a4e2cb
commit
bc81fcceda
@@ -1013,6 +1013,74 @@ func ICMPv6Payload(want []byte) TransportChecker {
|
||||
}
|
||||
}
|
||||
|
||||
// MLD creates a checker that checks that the packet contains a valid MLD
|
||||
// message for type of mldType, with potentially additional checks specified by
|
||||
// checkers.
|
||||
//
|
||||
// Checkers may assume that a valid ICMPv6 is passed to it containing a valid
|
||||
// MLD message as far as the size of the message (minSize) is concerned. The
|
||||
// values within the message are up to checkers to validate.
|
||||
func MLD(msgType header.ICMPv6Type, minSize int, checkers ...TransportChecker) NetworkChecker {
|
||||
return func(t *testing.T, h []header.Network) {
|
||||
t.Helper()
|
||||
|
||||
// Check normal ICMPv6 first.
|
||||
ICMPv6(
|
||||
ICMPv6Type(msgType),
|
||||
ICMPv6Code(0))(t, h)
|
||||
|
||||
last := h[len(h)-1]
|
||||
|
||||
icmp := header.ICMPv6(last.Payload())
|
||||
if got := len(icmp.MessageBody()); got < minSize {
|
||||
t.Fatalf("ICMPv6 MLD (type = %d) payload size of %d is less than the minimum size of %d", msgType, got, minSize)
|
||||
}
|
||||
|
||||
for _, f := range checkers {
|
||||
f(t, icmp)
|
||||
}
|
||||
if t.Failed() {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MLDMaxRespDelay creates a checker that checks the Maximum Response Delay
|
||||
// field of a MLD message.
|
||||
//
|
||||
// The returned TransportChecker assumes that a valid ICMPv6 is passed to it
|
||||
// containing a valid MLD message as far as the size is concerned.
|
||||
func MLDMaxRespDelay(want time.Duration) TransportChecker {
|
||||
return func(t *testing.T, h header.Transport) {
|
||||
t.Helper()
|
||||
|
||||
icmp := h.(header.ICMPv6)
|
||||
ns := header.MLD(icmp.MessageBody())
|
||||
|
||||
if got := ns.MaximumResponseDelay(); got != want {
|
||||
t.Errorf("got %T.MaximumResponseDelay() = %s, want = %s", ns, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MLDMulticastAddress creates a checker that checks the Multicast Address
|
||||
// field of a MLD message.
|
||||
//
|
||||
// The returned TransportChecker assumes that a valid ICMPv6 is passed to it
|
||||
// containing a valid MLD message as far as the size is concerned.
|
||||
func MLDMulticastAddress(want tcpip.Address) TransportChecker {
|
||||
return func(t *testing.T, h header.Transport) {
|
||||
t.Helper()
|
||||
|
||||
icmp := h.(header.ICMPv6)
|
||||
ns := header.MLD(icmp.MessageBody())
|
||||
|
||||
if got := ns.MulticastAddress(); got != want {
|
||||
t.Errorf("got %T.MulticastAddress() = %s, want = %s", ns, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NDP creates a checker that checks that the packet contains a valid NDP
|
||||
// message for type of ty, with potentially additional checks specified by
|
||||
// checkers.
|
||||
@@ -1032,7 +1100,7 @@ func NDP(msgType header.ICMPv6Type, minSize int, checkers ...TransportChecker) N
|
||||
last := h[len(h)-1]
|
||||
|
||||
icmp := header.ICMPv6(last.Payload())
|
||||
if got := len(icmp.NDPPayload()); got < minSize {
|
||||
if got := len(icmp.MessageBody()); got < minSize {
|
||||
t.Fatalf("ICMPv6 NDP (type = %d) payload size of %d is less than the minimum size of %d", msgType, got, minSize)
|
||||
}
|
||||
|
||||
@@ -1066,7 +1134,7 @@ func NDPNSTargetAddress(want tcpip.Address) TransportChecker {
|
||||
t.Helper()
|
||||
|
||||
icmp := h.(header.ICMPv6)
|
||||
ns := header.NDPNeighborSolicit(icmp.NDPPayload())
|
||||
ns := header.NDPNeighborSolicit(icmp.MessageBody())
|
||||
|
||||
if got := ns.TargetAddress(); got != want {
|
||||
t.Errorf("got %T.TargetAddress() = %s, want = %s", ns, got, want)
|
||||
@@ -1095,7 +1163,7 @@ func NDPNATargetAddress(want tcpip.Address) TransportChecker {
|
||||
t.Helper()
|
||||
|
||||
icmp := h.(header.ICMPv6)
|
||||
na := header.NDPNeighborAdvert(icmp.NDPPayload())
|
||||
na := header.NDPNeighborAdvert(icmp.MessageBody())
|
||||
|
||||
if got := na.TargetAddress(); got != want {
|
||||
t.Errorf("got %T.TargetAddress() = %s, want = %s", na, got, want)
|
||||
@@ -1113,7 +1181,7 @@ func NDPNASolicitedFlag(want bool) TransportChecker {
|
||||
t.Helper()
|
||||
|
||||
icmp := h.(header.ICMPv6)
|
||||
na := header.NDPNeighborAdvert(icmp.NDPPayload())
|
||||
na := header.NDPNeighborAdvert(icmp.MessageBody())
|
||||
|
||||
if got := na.SolicitedFlag(); got != want {
|
||||
t.Errorf("got %T.SolicitedFlag = %t, want = %t", na, got, want)
|
||||
@@ -1184,7 +1252,7 @@ func NDPNAOptions(opts []header.NDPOption) TransportChecker {
|
||||
t.Helper()
|
||||
|
||||
icmp := h.(header.ICMPv6)
|
||||
na := header.NDPNeighborAdvert(icmp.NDPPayload())
|
||||
na := header.NDPNeighborAdvert(icmp.MessageBody())
|
||||
ndpOptions(t, na.Options(), opts)
|
||||
}
|
||||
}
|
||||
@@ -1199,7 +1267,7 @@ func NDPNSOptions(opts []header.NDPOption) TransportChecker {
|
||||
t.Helper()
|
||||
|
||||
icmp := h.(header.ICMPv6)
|
||||
ns := header.NDPNeighborSolicit(icmp.NDPPayload())
|
||||
ns := header.NDPNeighborSolicit(icmp.MessageBody())
|
||||
ndpOptions(t, ns.Options(), opts)
|
||||
}
|
||||
}
|
||||
@@ -1224,7 +1292,7 @@ func NDPRSOptions(opts []header.NDPOption) TransportChecker {
|
||||
t.Helper()
|
||||
|
||||
icmp := h.(header.ICMPv6)
|
||||
rs := header.NDPRouterSolicit(icmp.NDPPayload())
|
||||
rs := header.NDPRouterSolicit(icmp.MessageBody())
|
||||
ndpOptions(t, rs.Options(), opts)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +115,12 @@ const (
|
||||
ICMPv6NeighborSolicit ICMPv6Type = 135
|
||||
ICMPv6NeighborAdvert ICMPv6Type = 136
|
||||
ICMPv6RedirectMsg ICMPv6Type = 137
|
||||
|
||||
// Multicast Listener Discovery (MLD) messages, see RFC 2710.
|
||||
|
||||
ICMPv6MulticastListenerQuery ICMPv6Type = 130
|
||||
ICMPv6MulticastListenerReport ICMPv6Type = 131
|
||||
ICMPv6MulticastListenerDone ICMPv6Type = 132
|
||||
)
|
||||
|
||||
// IsErrorType returns true if the receiver is an ICMP error type.
|
||||
@@ -245,10 +251,9 @@ func (b ICMPv6) SetSequence(sequence uint16) {
|
||||
binary.BigEndian.PutUint16(b[icmpv6SequenceOffset:], sequence)
|
||||
}
|
||||
|
||||
// NDPPayload returns the NDP payload buffer. That is, it returns the ICMPv6
|
||||
// packet's message body as defined by RFC 4443 section 2.1; the portion of the
|
||||
// ICMPv6 buffer after the first ICMPv6HeaderSize bytes.
|
||||
func (b ICMPv6) NDPPayload() []byte {
|
||||
// MessageBody returns the message body as defined by RFC 4443 section 2.1; the
|
||||
// portion of the ICMPv6 buffer after the first ICMPv6HeaderSize bytes.
|
||||
func (b ICMPv6) MessageBody() []byte {
|
||||
return b[ICMPv6HeaderSize:]
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,13 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// MLDMinimumSize is the minimum size for an MLD message.
|
||||
MLDMinimumSize = 20
|
||||
|
||||
// MLDHopLimit is the Hop Limit for all IPv6 packets with an MLD message, as
|
||||
// per RFC 2710 section 3.
|
||||
MLDHopLimit = 1
|
||||
|
||||
// mldMaximumResponseDelayOffset is the offset to the Maximum Response Delay
|
||||
// field within MLD.
|
||||
mldMaximumResponseDelayOffset = 0
|
||||
|
||||
@@ -7,12 +7,14 @@ go_test(
|
||||
size = "small",
|
||||
srcs = [
|
||||
"ip_test.go",
|
||||
"multicast_group_test.go",
|
||||
],
|
||||
deps = [
|
||||
"//pkg/sync",
|
||||
"//pkg/tcpip",
|
||||
"//pkg/tcpip/buffer",
|
||||
"//pkg/tcpip/checker",
|
||||
"//pkg/tcpip/faketime",
|
||||
"//pkg/tcpip/header",
|
||||
"//pkg/tcpip/header/parse",
|
||||
"//pkg/tcpip/link/channel",
|
||||
|
||||
@@ -51,6 +51,16 @@ const (
|
||||
UnsolicitedReportIntervalMax = 10 * time.Second
|
||||
)
|
||||
|
||||
// IGMPOptions holds options for IGMP.
|
||||
type IGMPOptions struct {
|
||||
// Enabled indicates whether IGMP will be performed.
|
||||
//
|
||||
// When enabled, IGMP may transmit IGMP report and leave messages when
|
||||
// joining and leaving multicast groups respectively, and handle incoming
|
||||
// IGMP packets.
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
var _ ip.MulticastGroupProtocol = (*igmpState)(nil)
|
||||
|
||||
// igmpState is the per-interface IGMP state.
|
||||
@@ -58,7 +68,8 @@ var _ ip.MulticastGroupProtocol = (*igmpState)(nil)
|
||||
// igmpState.init() MUST be called after creating an IGMP state.
|
||||
type igmpState struct {
|
||||
// The IPv4 endpoint this igmpState is for.
|
||||
ep *endpoint
|
||||
ep *endpoint
|
||||
opts IGMPOptions
|
||||
|
||||
// igmpV1Present is for maintaining compatibility with IGMPv1 Routers, from
|
||||
// RFC 2236 Section 4 Page 6: "The IGMPv1 router expects Version 1
|
||||
@@ -108,10 +119,11 @@ func (igmp *igmpState) SendLeave(groupAddress tcpip.Address) *tcpip.Error {
|
||||
|
||||
// init sets up an igmpState struct, and is required to be called before using
|
||||
// a new igmpState.
|
||||
func (igmp *igmpState) init(ep *endpoint) {
|
||||
func (igmp *igmpState) init(ep *endpoint, opts IGMPOptions) {
|
||||
igmp.mu.Lock()
|
||||
defer igmp.mu.Unlock()
|
||||
igmp.ep = ep
|
||||
igmp.opts = opts
|
||||
igmp.mu.genericMulticastProtocol.Init(ep.protocol.stack.Rand(), ep.protocol.stack.Clock(), igmp, UnsolicitedReportIntervalMax)
|
||||
igmp.igmpV1Present = igmpV1PresentDefault
|
||||
igmp.mu.igmpV1Job = igmp.ep.protocol.stack.NewJob(&igmp.mu, func() {
|
||||
@@ -189,6 +201,10 @@ func (igmp *igmpState) setV1Present(v bool) {
|
||||
}
|
||||
|
||||
func (igmp *igmpState) handleMembershipQuery(groupAddress tcpip.Address, maxRespTime time.Duration) {
|
||||
if !igmp.opts.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
igmp.mu.Lock()
|
||||
defer igmp.mu.Unlock()
|
||||
|
||||
@@ -206,6 +222,10 @@ func (igmp *igmpState) handleMembershipQuery(groupAddress tcpip.Address, maxResp
|
||||
}
|
||||
|
||||
func (igmp *igmpState) handleMembershipReport(groupAddress tcpip.Address) {
|
||||
if !igmp.opts.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
igmp.mu.Lock()
|
||||
defer igmp.mu.Unlock()
|
||||
igmp.mu.genericMulticastProtocol.HandleReport(groupAddress)
|
||||
@@ -226,11 +246,8 @@ func (igmp *igmpState) writePacket(destAddress tcpip.Address, groupAddress tcpip
|
||||
|
||||
// TODO(gvisor.dev/issue/4888): We should not use the unspecified address,
|
||||
// rather we should select an appropriate local address.
|
||||
r := stack.Route{
|
||||
LocalAddress: header.IPv4Any,
|
||||
RemoteAddress: destAddress,
|
||||
}
|
||||
igmp.ep.addIPHeader(&r, pkt, stack.NetworkHeaderParams{
|
||||
localAddr := header.IPv4Any
|
||||
igmp.ep.addIPHeader(localAddr, destAddress, pkt, stack.NetworkHeaderParams{
|
||||
Protocol: header.IGMPProtocolNumber,
|
||||
TTL: header.IGMPTTL,
|
||||
TOS: stack.DefaultTOS,
|
||||
@@ -239,7 +256,7 @@ func (igmp *igmpState) writePacket(destAddress tcpip.Address, groupAddress tcpip
|
||||
// TODO(b/162198658): set the ROUTER_ALERT option when sending Host
|
||||
// Membership Reports.
|
||||
sent := igmp.ep.protocol.stack.Stats().IGMP.PacketsSent
|
||||
if err := igmp.ep.nic.WritePacketToRemote(header.EthernetAddressFromMulticastIPv4Address(destAddress), nil /* gso */, header.IPv4ProtocolNumber, pkt); err != nil {
|
||||
if err := igmp.ep.nic.WritePacketToRemote(header.EthernetAddressFromMulticastIPv4Address(destAddress), nil /* gso */, ProtocolNumber, pkt); err != nil {
|
||||
sent.Dropped.Increment()
|
||||
return err
|
||||
}
|
||||
@@ -263,6 +280,26 @@ func (igmp *igmpState) writePacket(destAddress tcpip.Address, groupAddress tcpip
|
||||
// If the group already exists in the membership map, returns
|
||||
// tcpip.ErrDuplicateAddress.
|
||||
func (igmp *igmpState) joinGroup(groupAddress tcpip.Address) *tcpip.Error {
|
||||
if !igmp.opts.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
// As per RFC 2236 section 6 page 10,
|
||||
//
|
||||
// The all-systems group (address 224.0.0.1) is handled as a special
|
||||
// case. The host starts in Idle Member state for that group on every
|
||||
// interface, never transitions to another state, and never sends a
|
||||
// report for that group.
|
||||
//
|
||||
// This is equivalent to not performing IGMP for the all-systems multicast
|
||||
// address. Simply not performing IGMP when the group is added will prevent
|
||||
// any work from being done on the all-systems multicast group when leaving
|
||||
// the group or when query or report messages are received for it since the
|
||||
// MGP state will not know about it.
|
||||
if groupAddress == header.IPv4AllSystems {
|
||||
return nil
|
||||
}
|
||||
|
||||
igmp.mu.Lock()
|
||||
defer igmp.mu.Unlock()
|
||||
|
||||
@@ -280,6 +317,10 @@ func (igmp *igmpState) joinGroup(groupAddress tcpip.Address) *tcpip.Error {
|
||||
// If the group does not exist in the membership map, this function will
|
||||
// silently return.
|
||||
func (igmp *igmpState) leaveGroup(groupAddress tcpip.Address) {
|
||||
if !igmp.opts.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
igmp.mu.Lock()
|
||||
defer igmp.mu.Unlock()
|
||||
igmp.mu.genericMulticastProtocol.LeaveGroup(groupAddress)
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
package ipv4_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
@@ -30,25 +28,11 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
linkAddr = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06")
|
||||
// endpointAddr = tcpip.Address("\x0a\x00\x00\x02")
|
||||
linkAddr = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06")
|
||||
multicastAddr = tcpip.Address("\xe0\x00\x00\x03")
|
||||
nicID = 1
|
||||
)
|
||||
|
||||
var (
|
||||
// unsolicitedReportIntervalMaxTenthSec is the maximum amount of time the NIC
|
||||
// will wait before sending an unsolicited report after joining a multicast
|
||||
// group, in deciseconds.
|
||||
unsolicitedReportIntervalMaxTenthSec = func() uint8 {
|
||||
const decisecond = time.Second / 10
|
||||
if ipv4.UnsolicitedReportIntervalMax%decisecond != 0 {
|
||||
panic(fmt.Sprintf("UnsolicitedReportIntervalMax of %d is a lossy conversion to deciseconds", ipv4.UnsolicitedReportIntervalMax))
|
||||
}
|
||||
return uint8(ipv4.UnsolicitedReportIntervalMax / decisecond)
|
||||
}()
|
||||
)
|
||||
|
||||
// validateIgmpPacket checks that a passed PacketInfo is an IPv4 IGMP packet
|
||||
// sent to the provided address with the passed fields set. Raises a t.Error if
|
||||
// any field does not match.
|
||||
@@ -75,7 +59,9 @@ func createStack(t *testing.T, igmpEnabled bool) (*channel.Endpoint, *stack.Stac
|
||||
clock := faketime.NewManualClock()
|
||||
s := stack.New(stack.Options{
|
||||
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocolWithOptions(ipv4.Options{
|
||||
IGMPEnabled: igmpEnabled,
|
||||
IGMP: ipv4.IGMPOptions{
|
||||
Enabled: igmpEnabled,
|
||||
},
|
||||
})},
|
||||
Clock: clock,
|
||||
})
|
||||
@@ -110,339 +96,6 @@ func createAndInjectIGMPPacket(e *channel.Endpoint, igmpType header.IGMPType, ma
|
||||
})
|
||||
}
|
||||
|
||||
// TestIgmpDisabled tests that IGMP is not enabled with a default
|
||||
// stack.Options. This also tests that this NIC does not send the IGMP Join
|
||||
// Group for the All Hosts group it automatically joins when created.
|
||||
func TestIgmpDisabled(t *testing.T) {
|
||||
e, s, _ := createStack(t, false)
|
||||
|
||||
// This NIC will join the All Hosts group when created. Verify that does not
|
||||
// send a report.
|
||||
if got := s.Stats().IGMP.PacketsSent.V2MembershipReport.Value(); got != 0 {
|
||||
t.Fatalf("got V2MembershipReport messages sent = %d, want = 0", got)
|
||||
}
|
||||
p, ok := e.Read()
|
||||
if ok {
|
||||
t.Fatalf("sent unexpected packet, stack with disabled IGMP sent packet = %+v", p.Pkt)
|
||||
}
|
||||
|
||||
// Test joining a specific group explicitly and verify that no reports are
|
||||
// sent.
|
||||
if err := s.JoinGroup(ipv4.ProtocolNumber, nicID, multicastAddr); err != nil {
|
||||
t.Fatalf("JoinGroup(ipv4.ProtocolNumber, %d, %s) = %s", nicID, multicastAddr, err)
|
||||
}
|
||||
|
||||
if got := s.Stats().IGMP.PacketsSent.V2MembershipReport.Value(); got != 0 {
|
||||
t.Fatalf("got V2MembershipReport messages sent = %d, want = 0", got)
|
||||
}
|
||||
p, ok = e.Read()
|
||||
if ok {
|
||||
t.Fatalf("sent unexpected packet, stack with disabled IGMP sent packet = %+v", p.Pkt)
|
||||
}
|
||||
|
||||
// Inject a General Membership Query, which is an IGMP Membership Query with
|
||||
// a zeroed Group Address (IPv4Any) to verify that it does not reach the
|
||||
// handler.
|
||||
createAndInjectIGMPPacket(e, header.IGMPMembershipQuery, unsolicitedReportIntervalMaxTenthSec, header.IPv4Any)
|
||||
|
||||
if got := s.Stats().IGMP.PacketsReceived.MembershipQuery.Value(); got != 0 {
|
||||
t.Fatalf("got Membership Queries received = %d, want = 0", got)
|
||||
}
|
||||
p, ok = e.Read()
|
||||
if ok {
|
||||
t.Fatalf("sent unexpected packet, stack with disabled IGMP sent packet = %+v", p.Pkt)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIgmpReceivesIGMPMessages tests that the IGMP stack increments packet
|
||||
// counters when it receives properly formatted Membership Queries, Membership
|
||||
// Reports, and LeaveGroup Messages sent to this address. Note: test includes
|
||||
// IGMP header fields that are not explicitly tested in order to inject proper
|
||||
// IGMP packets.
|
||||
func TestIgmpReceivesIGMPMessages(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
headerType header.IGMPType
|
||||
maxRespTime byte
|
||||
groupAddress tcpip.Address
|
||||
statCounter func(tcpip.IGMPReceivedPacketStats) *tcpip.StatCounter
|
||||
}{
|
||||
{
|
||||
name: "General Membership Query",
|
||||
headerType: header.IGMPMembershipQuery,
|
||||
maxRespTime: unsolicitedReportIntervalMaxTenthSec,
|
||||
groupAddress: header.IPv4Any,
|
||||
statCounter: func(stats tcpip.IGMPReceivedPacketStats) *tcpip.StatCounter {
|
||||
return stats.MembershipQuery
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "IGMPv1 Membership Report",
|
||||
headerType: header.IGMPv1MembershipReport,
|
||||
maxRespTime: 0,
|
||||
groupAddress: header.IPv4AllSystems,
|
||||
statCounter: func(stats tcpip.IGMPReceivedPacketStats) *tcpip.StatCounter {
|
||||
return stats.V1MembershipReport
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "IGMPv2 Membership Report",
|
||||
headerType: header.IGMPv2MembershipReport,
|
||||
maxRespTime: 0,
|
||||
groupAddress: header.IPv4AllSystems,
|
||||
statCounter: func(stats tcpip.IGMPReceivedPacketStats) *tcpip.StatCounter {
|
||||
return stats.V2MembershipReport
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Leave Group",
|
||||
headerType: header.IGMPLeaveGroup,
|
||||
maxRespTime: 0,
|
||||
groupAddress: header.IPv4AllRoutersGroup,
|
||||
statCounter: func(stats tcpip.IGMPReceivedPacketStats) *tcpip.StatCounter {
|
||||
return stats.LeaveGroup
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
e, s, _ := createStack(t, true)
|
||||
|
||||
createAndInjectIGMPPacket(e, test.headerType, test.maxRespTime, test.groupAddress)
|
||||
|
||||
if got := test.statCounter(s.Stats().IGMP.PacketsReceived).Value(); got != 1 {
|
||||
t.Fatalf("got %s received = %d, want = 1", test.name, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestIgmpJoinGroup tests that when explicitly joining a multicast group, the
|
||||
// IGMP stack schedules and sends correct Membership Reports.
|
||||
func TestIgmpJoinGroup(t *testing.T) {
|
||||
e, s, clock := createStack(t, true)
|
||||
|
||||
// Test joining a specific address explicitly and verify a Membership Report
|
||||
// is sent immediately.
|
||||
if err := s.JoinGroup(ipv4.ProtocolNumber, nicID, multicastAddr); err != nil {
|
||||
t.Fatalf("JoinGroup(ipv4, nic, %s) = %s", multicastAddr, err)
|
||||
}
|
||||
|
||||
p, ok := e.Read()
|
||||
if !ok {
|
||||
t.Fatal("unable to Read IGMP packet, expected V2MembershipReport")
|
||||
}
|
||||
if got := s.Stats().IGMP.PacketsSent.V2MembershipReport.Value(); got != 1 {
|
||||
t.Fatalf("got V2MembershipReport messages sent = %d, want = 1", got)
|
||||
}
|
||||
|
||||
validateIgmpPacket(t, p, multicastAddr, header.IGMPv2MembershipReport, 0, multicastAddr)
|
||||
if t.Failed() {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// Verify the second Membership Report is sent after a random interval up to
|
||||
// the maximum unsolicited report interval.
|
||||
p, ok = e.Read()
|
||||
if ok {
|
||||
t.Fatalf("sent unexpected packet, expected V2MembershipReport only after advancing the clock = %+v", p.Pkt)
|
||||
}
|
||||
clock.Advance(ipv4.UnsolicitedReportIntervalMax)
|
||||
p, ok = e.Read()
|
||||
if !ok {
|
||||
t.Fatal("unable to Read IGMP packet, expected V2MembershipReport")
|
||||
}
|
||||
if got := s.Stats().IGMP.PacketsSent.V2MembershipReport.Value(); got != 2 {
|
||||
t.Fatalf("got V2MembershipReport messages sent = %d, want = 2", got)
|
||||
}
|
||||
validateIgmpPacket(t, p, multicastAddr, header.IGMPv2MembershipReport, 0, multicastAddr)
|
||||
}
|
||||
|
||||
// TestIgmpLeaveGroup tests that when leaving a previously joined multicast
|
||||
// group the IGMP enabled NIC sends the appropriate message.
|
||||
func TestIgmpLeaveGroup(t *testing.T) {
|
||||
e, s, clock := createStack(t, true)
|
||||
|
||||
// Join a group so that it can be left, validate the immediate Membership
|
||||
// Report is sent only to the multicast address joined.
|
||||
if err := s.JoinGroup(ipv4.ProtocolNumber, nicID, multicastAddr); err != nil {
|
||||
t.Fatalf("JoinGroup(ipv4, nic, %s) = %s", multicastAddr, err)
|
||||
}
|
||||
p, ok := e.Read()
|
||||
if !ok {
|
||||
t.Fatal("unable to Read IGMP packet, expected V2MembershipReport")
|
||||
}
|
||||
if got := s.Stats().IGMP.PacketsSent.V2MembershipReport.Value(); got != 1 {
|
||||
t.Fatalf("got V2MembershipReport messages sent = %d, want = 1", got)
|
||||
}
|
||||
validateIgmpPacket(t, p, multicastAddr, header.IGMPv2MembershipReport, 0, multicastAddr)
|
||||
if t.Failed() {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// Verify the second Membership Report is sent after a random interval up to
|
||||
// the maximum unsolicited report interval, and is sent to the multicast
|
||||
// address being joined.
|
||||
p, ok = e.Read()
|
||||
if ok {
|
||||
t.Fatalf("sent unexpected packet, expected V2MembershipReport only after advancing the clock = %+v", p.Pkt)
|
||||
}
|
||||
clock.Advance(ipv4.UnsolicitedReportIntervalMax)
|
||||
p, ok = e.Read()
|
||||
if !ok {
|
||||
t.Fatal("unable to Read IGMP packet, expected V2MembershipReport")
|
||||
}
|
||||
if got := s.Stats().IGMP.PacketsSent.V2MembershipReport.Value(); got != 2 {
|
||||
t.Fatalf("got V2MembershipReport messages sent = %d, want = 2", got)
|
||||
}
|
||||
validateIgmpPacket(t, p, multicastAddr, header.IGMPv2MembershipReport, 0, multicastAddr)
|
||||
if t.Failed() {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// Now that there are no packets queued and none scheduled to be sent, leave
|
||||
// the group.
|
||||
if err := s.LeaveGroup(ipv4.ProtocolNumber, nicID, multicastAddr); err != nil {
|
||||
t.Fatalf("LeaveGroup(ipv4, nic, %s) = %s", multicastAddr, err)
|
||||
}
|
||||
|
||||
// Observe the Leave Group Message to verify that the Leave Group message is
|
||||
// sent to the All Routers group but that the message itself has the
|
||||
// multicast address being left.
|
||||
p, ok = e.Read()
|
||||
if !ok {
|
||||
t.Fatal("unable to Read IGMP packet, expected LeaveGroup")
|
||||
}
|
||||
if got := s.Stats().IGMP.PacketsSent.LeaveGroup.Value(); got != 1 {
|
||||
t.Fatalf("got LeaveGroup messages sent = %d, want = 1", got)
|
||||
}
|
||||
validateIgmpPacket(t, p, header.IPv4AllRoutersGroup, header.IGMPLeaveGroup, 0, multicastAddr)
|
||||
}
|
||||
|
||||
// TestIgmpJoinLeaveGroup tests that when leaving a previously joined multicast
|
||||
// group before the Unsolicited Report Interval cancels the second membership
|
||||
// report.
|
||||
func TestIgmpJoinLeaveGroup(t *testing.T) {
|
||||
_, s, clock := createStack(t, true)
|
||||
|
||||
if err := s.JoinGroup(ipv4.ProtocolNumber, nicID, multicastAddr); err != nil {
|
||||
t.Fatalf("JoinGroup(ipv4, nic, %s) = %s", multicastAddr, err)
|
||||
}
|
||||
|
||||
// Verify that this NIC sent a Membership Report for only the group just
|
||||
// joined.
|
||||
if got := s.Stats().IGMP.PacketsSent.V2MembershipReport.Value(); got != 1 {
|
||||
t.Fatalf("got V2MembershipReport messages sent = %d, want = 1", got)
|
||||
}
|
||||
|
||||
if err := s.LeaveGroup(ipv4.ProtocolNumber, nicID, multicastAddr); err != nil {
|
||||
t.Fatalf("LeaveGroup(ipv4, nic, %s) = %s", multicastAddr, err)
|
||||
}
|
||||
|
||||
// Wait for the standard IGMP Unsolicited Report Interval duration before
|
||||
// verifying that the unsolicited Membership Report was sent after leaving
|
||||
// the group.
|
||||
clock.Advance(ipv4.UnsolicitedReportIntervalMax)
|
||||
if got := s.Stats().IGMP.PacketsSent.V2MembershipReport.Value(); got != 1 {
|
||||
t.Fatalf("got V2MembershipReport messages sent = %d, want = 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIgmpMembershipQueryReport tests the handling of both incoming IGMP
|
||||
// Membership Queries and outgoing Membership Reports.
|
||||
func TestIgmpMembershipQueryReport(t *testing.T) {
|
||||
e, s, clock := createStack(t, true)
|
||||
|
||||
if err := s.JoinGroup(ipv4.ProtocolNumber, nicID, multicastAddr); err != nil {
|
||||
t.Fatalf("JoinGroup(ipv4, nic, %s) = %s", multicastAddr, err)
|
||||
}
|
||||
|
||||
p, ok := e.Read()
|
||||
if !ok {
|
||||
t.Fatal("unable to Read IGMP packet, expected V2MembershipReport")
|
||||
}
|
||||
if got := s.Stats().IGMP.PacketsSent.V2MembershipReport.Value(); got != 1 {
|
||||
t.Fatalf("got V2MembershipReport messages sent = %d, want = 1", got)
|
||||
}
|
||||
validateIgmpPacket(t, p, multicastAddr, header.IGMPv2MembershipReport, 0, multicastAddr)
|
||||
if t.Failed() {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
p, ok = e.Read()
|
||||
if ok {
|
||||
t.Fatalf("sent unexpected packet, expected V2MembershipReport only after advancing the clock = %+v", p.Pkt)
|
||||
}
|
||||
clock.Advance(ipv4.UnsolicitedReportIntervalMax)
|
||||
p, ok = e.Read()
|
||||
if !ok {
|
||||
t.Fatal("unable to Read IGMP packet, expected V2MembershipReport")
|
||||
}
|
||||
if got := s.Stats().IGMP.PacketsSent.V2MembershipReport.Value(); got != 2 {
|
||||
t.Fatalf("got V2MembershipReport messages sent = %d, want = 2", got)
|
||||
}
|
||||
validateIgmpPacket(t, p, multicastAddr, header.IGMPv2MembershipReport, 0, multicastAddr)
|
||||
|
||||
// Inject a General Membership Query, which is an IGMP Membership Query with
|
||||
// a zeroed Group Address (IPv4Any) with the shortened Max Response Time.
|
||||
const maxRespTimeDS = 10
|
||||
createAndInjectIGMPPacket(e, header.IGMPMembershipQuery, maxRespTimeDS, header.IPv4Any)
|
||||
|
||||
p, ok = e.Read()
|
||||
if ok {
|
||||
t.Fatalf("sent unexpected packet, expected V2MembershipReport only after advancing the clock = %+v", p.Pkt)
|
||||
}
|
||||
clock.Advance(header.DecisecondToDuration(maxRespTimeDS))
|
||||
p, ok = e.Read()
|
||||
if !ok {
|
||||
t.Fatal("unable to Read IGMP packet, expected V2MembershipReport")
|
||||
}
|
||||
if got := s.Stats().IGMP.PacketsSent.V2MembershipReport.Value(); got != 3 {
|
||||
t.Fatalf("got V2MembershipReport messages sent = %d, want = 3", got)
|
||||
}
|
||||
validateIgmpPacket(t, p, multicastAddr, header.IGMPv2MembershipReport, 0, multicastAddr)
|
||||
}
|
||||
|
||||
// TestIgmpMultipleHosts tests the handling of IGMP Leave when we are not the
|
||||
// most recent IGMP host to join a multicast network.
|
||||
func TestIgmpMultipleHosts(t *testing.T) {
|
||||
e, s, clock := createStack(t, true)
|
||||
|
||||
if err := s.JoinGroup(ipv4.ProtocolNumber, nicID, multicastAddr); err != nil {
|
||||
t.Fatalf("JoinGroup(ipv4, nic, %s) = %s", multicastAddr, err)
|
||||
}
|
||||
|
||||
p, ok := e.Read()
|
||||
if !ok {
|
||||
t.Fatal("unable to Read IGMP packet, expected V2MembershipReport")
|
||||
}
|
||||
if got := s.Stats().IGMP.PacketsSent.V2MembershipReport.Value(); got != 1 {
|
||||
t.Fatalf("got V2MembershipReport messages sent = %d, want = 1", got)
|
||||
}
|
||||
validateIgmpPacket(t, p, multicastAddr, header.IGMPv2MembershipReport, 0, multicastAddr)
|
||||
if t.Failed() {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// Inject another Host's Join Group message so that this host is not the
|
||||
// latest to send the report. Set Max Response Time to 0 for Membership
|
||||
// Reports.
|
||||
createAndInjectIGMPPacket(e, header.IGMPv2MembershipReport, 0, multicastAddr)
|
||||
|
||||
if err := s.LeaveGroup(ipv4.ProtocolNumber, nicID, multicastAddr); err != nil {
|
||||
t.Fatalf("LeaveGroup(ipv4, nic, %s) = %s", multicastAddr, err)
|
||||
}
|
||||
|
||||
// Wait to be sure that no Leave Group messages were sent up to the max
|
||||
// unsolicited report interval since it was not the last host to join this
|
||||
// group.
|
||||
clock.Advance(ipv4.UnsolicitedReportIntervalMax)
|
||||
if got := s.Stats().IGMP.PacketsSent.LeaveGroup.Value(); got != 0 {
|
||||
t.Fatalf("got LeaveGroup messages sent = %d, want = 0", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIgmpV1Present tests the handling of the case where an IGMPv1 router is
|
||||
// present on the network. The IGMP stack will then send IGMPv1 Membership
|
||||
// reports for backwards compatibility.
|
||||
|
||||
@@ -95,7 +95,7 @@ func (p *protocol) NewEndpoint(nic stack.NetworkInterface, _ stack.LinkAddressCa
|
||||
protocol: p,
|
||||
}
|
||||
e.mu.addressableEndpointState.Init(e)
|
||||
e.igmp.init(e)
|
||||
e.igmp.init(e, p.options.IGMP)
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ func (e *endpoint) Enable() *tcpip.Error {
|
||||
// As per RFC 1122 section 3.3.7, all hosts should join the all-hosts
|
||||
// multicast group. Note, the IANA calls the all-hosts multicast group the
|
||||
// all-systems multicast group.
|
||||
_, err = e.mu.addressableEndpointState.JoinGroup(header.IPv4AllSystems)
|
||||
_, err = e.joinGroupLocked(header.IPv4AllSystems)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ func (e *endpoint) disableLocked() {
|
||||
}
|
||||
|
||||
// The endpoint may have already left the multicast group.
|
||||
if _, err := e.mu.addressableEndpointState.LeaveGroup(header.IPv4AllSystems); err != nil && err != tcpip.ErrBadLocalAddress {
|
||||
if _, err := e.leaveGroupLocked(header.IPv4AllSystems); err != nil && err != tcpip.ErrBadLocalAddress {
|
||||
panic(fmt.Sprintf("unexpected error when leaving group = %s: %s", header.IPv4AllSystems, err))
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ func (e *endpoint) NetworkProtocolNumber() tcpip.NetworkProtocolNumber {
|
||||
return e.protocol.Number()
|
||||
}
|
||||
|
||||
func (e *endpoint) addIPHeader(r *stack.Route, pkt *stack.PacketBuffer, params stack.NetworkHeaderParams) {
|
||||
func (e *endpoint) addIPHeader(srcAddr, dstAddr tcpip.Address, pkt *stack.PacketBuffer, params stack.NetworkHeaderParams) {
|
||||
hdrLen := header.IPv4MinimumSize
|
||||
var opts header.IPv4Options
|
||||
if params.Options != nil {
|
||||
@@ -221,15 +221,15 @@ func (e *endpoint) addIPHeader(r *stack.Route, pkt *stack.PacketBuffer, params s
|
||||
// RFC 6864 section 4.3 mandates uniqueness of ID values for non-atomic
|
||||
// datagrams. Since the DF bit is never being set here, all datagrams
|
||||
// are non-atomic and need an ID.
|
||||
id := atomic.AddUint32(&e.protocol.ids[hashRoute(r, params.Protocol, e.protocol.hashIV)%buckets], 1)
|
||||
id := atomic.AddUint32(&e.protocol.ids[hashRoute(srcAddr, dstAddr, params.Protocol, e.protocol.hashIV)%buckets], 1)
|
||||
ip.Encode(&header.IPv4Fields{
|
||||
TotalLength: length,
|
||||
ID: uint16(id),
|
||||
TTL: params.TTL,
|
||||
TOS: params.TOS,
|
||||
Protocol: uint8(params.Protocol),
|
||||
SrcAddr: r.LocalAddress,
|
||||
DstAddr: r.RemoteAddress,
|
||||
SrcAddr: srcAddr,
|
||||
DstAddr: dstAddr,
|
||||
Options: opts,
|
||||
})
|
||||
ip.SetChecksum(^ip.CalculateChecksum())
|
||||
@@ -261,7 +261,7 @@ func (e *endpoint) handleFragments(r *stack.Route, gso *stack.GSO, networkMTU ui
|
||||
|
||||
// WritePacket writes a packet to the given destination address and protocol.
|
||||
func (e *endpoint) WritePacket(r *stack.Route, gso *stack.GSO, params stack.NetworkHeaderParams, pkt *stack.PacketBuffer) *tcpip.Error {
|
||||
e.addIPHeader(r, pkt, params)
|
||||
e.addIPHeader(r.LocalAddress, r.RemoteAddress, pkt, params)
|
||||
|
||||
// iptables filtering. All packets that reach here are locally
|
||||
// generated.
|
||||
@@ -349,7 +349,7 @@ func (e *endpoint) WritePackets(r *stack.Route, gso *stack.GSO, pkts stack.Packe
|
||||
}
|
||||
|
||||
for pkt := pkts.Front(); pkt != nil; pkt = pkt.Next() {
|
||||
e.addIPHeader(r, pkt, params)
|
||||
e.addIPHeader(r.LocalAddress, r.RemoteAddress, pkt, params)
|
||||
networkMTU, err := calculateNetworkMTU(e.nic.MTU(), uint32(pkt.NetworkHeader().View().Size()))
|
||||
if err != nil {
|
||||
r.Stats().IP.OutgoingPacketErrors.IncrementBy(uint64(pkts.Len()))
|
||||
@@ -463,7 +463,7 @@ func (e *endpoint) WriteHeaderIncludedPacket(r *stack.Route, pkt *stack.PacketBu
|
||||
// non-atomic datagrams, so assign an ID to all such datagrams
|
||||
// according to the definition given in RFC 6864 section 4.
|
||||
if ip.Flags()&header.IPv4FlagDontFragment == 0 || ip.Flags()&header.IPv4FlagMoreFragments != 0 || ip.FragmentOffset() > 0 {
|
||||
ip.SetID(uint16(atomic.AddUint32(&e.protocol.ids[hashRoute(r, 0 /* protocol */, e.protocol.hashIV)%buckets], 1)))
|
||||
ip.SetID(uint16(atomic.AddUint32(&e.protocol.ids[hashRoute(r.LocalAddress, r.RemoteAddress, 0 /* protocol */, e.protocol.hashIV)%buckets], 1)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -706,10 +706,7 @@ func (e *endpoint) handlePacket(pkt *stack.PacketBuffer) {
|
||||
return
|
||||
}
|
||||
if p == header.IGMPProtocolNumber {
|
||||
if e.protocol.options.IGMPEnabled {
|
||||
e.igmp.handleIGMP(pkt)
|
||||
}
|
||||
// Nothing further to do with an IGMP packet, even if IGMP is not enabled.
|
||||
e.igmp.handleIGMP(pkt)
|
||||
return
|
||||
}
|
||||
if opts := h.Options(); len(opts) != 0 {
|
||||
@@ -837,32 +834,55 @@ func (e *endpoint) PermanentAddresses() []tcpip.AddressWithPrefix {
|
||||
|
||||
// JoinGroup implements stack.GroupAddressableEndpoint.
|
||||
func (e *endpoint) JoinGroup(addr tcpip.Address) (bool, *tcpip.Error) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
return e.joinGroupLocked(addr)
|
||||
}
|
||||
|
||||
// joinGroupLocked is like JoinGroup, but with locking requirements.
|
||||
//
|
||||
// Precondition: e.mu must be locked.
|
||||
func (e *endpoint) joinGroupLocked(addr tcpip.Address) (bool, *tcpip.Error) {
|
||||
if !header.IsV4MulticastAddress(addr) {
|
||||
return false, tcpip.ErrBadAddress
|
||||
}
|
||||
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
joinedGroup, err := e.mu.addressableEndpointState.JoinGroup(addr)
|
||||
if err == nil && joinedGroup && e.protocol.options.IGMPEnabled {
|
||||
_ = e.igmp.joinGroup(addr)
|
||||
// TODO(gvisor.dev/issue/4916): Keep track of join count and IGMP state in a
|
||||
// single type.
|
||||
joined, err := e.mu.addressableEndpointState.JoinGroup(addr)
|
||||
if err != nil || !joined {
|
||||
return joined, err
|
||||
}
|
||||
|
||||
return joinedGroup, err
|
||||
// joinGroup only returns an error if we try to join a group twice, but we
|
||||
// checked above to make sure that the group was newly joined.
|
||||
if err := e.igmp.joinGroup(addr); err != nil {
|
||||
panic(fmt.Sprintf("e.igmp.joinGroup(%s): %s", addr, err))
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// LeaveGroup implements stack.GroupAddressableEndpoint.
|
||||
func (e *endpoint) LeaveGroup(addr tcpip.Address) (bool, *tcpip.Error) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
return e.leaveGroupLocked(addr)
|
||||
}
|
||||
|
||||
leftGroup, err := e.mu.addressableEndpointState.LeaveGroup(addr)
|
||||
if err == nil && leftGroup && e.protocol.options.IGMPEnabled {
|
||||
// leaveGroupLocked is like LeaveGroup, but with locking requirements.
|
||||
//
|
||||
// Precondition: e.mu must be locked.
|
||||
func (e *endpoint) leaveGroupLocked(addr tcpip.Address) (bool, *tcpip.Error) {
|
||||
left, err := e.mu.addressableEndpointState.LeaveGroup(addr)
|
||||
if err != nil {
|
||||
return left, err
|
||||
}
|
||||
|
||||
if left {
|
||||
e.igmp.leaveGroup(addr)
|
||||
}
|
||||
|
||||
return leftGroup, err
|
||||
return left, nil
|
||||
}
|
||||
|
||||
// IsInGroup implements stack.GroupAddressableEndpoint.
|
||||
@@ -1021,20 +1041,19 @@ func addressToUint32(addr tcpip.Address) uint32 {
|
||||
return uint32(addr[0]) | uint32(addr[1])<<8 | uint32(addr[2])<<16 | uint32(addr[3])<<24
|
||||
}
|
||||
|
||||
// hashRoute calculates a hash value for the given route. It uses the source &
|
||||
// destination address, the transport protocol number and a 32-bit number to
|
||||
// generate the hash.
|
||||
func hashRoute(r *stack.Route, protocol tcpip.TransportProtocolNumber, hashIV uint32) uint32 {
|
||||
a := addressToUint32(r.LocalAddress)
|
||||
b := addressToUint32(r.RemoteAddress)
|
||||
// hashRoute calculates a hash value for the given source/destination pair using
|
||||
// the addresses, transport protocol number and a 32-bit number to generate the
|
||||
// hash.
|
||||
func hashRoute(srcAddr, dstAddr tcpip.Address, protocol tcpip.TransportProtocolNumber, hashIV uint32) uint32 {
|
||||
a := addressToUint32(srcAddr)
|
||||
b := addressToUint32(dstAddr)
|
||||
return hash.Hash3Words(a, b, uint32(protocol), hashIV)
|
||||
}
|
||||
|
||||
// Options holds options to configure a new protocol.
|
||||
type Options struct {
|
||||
// IGMPEnabled indicates whether incoming IGMP packets will be handled and if
|
||||
// this endpoint will transmit IGMP packets on IGMP related events.
|
||||
IGMPEnabled bool
|
||||
// IGMP holds options for IGMP.
|
||||
IGMP IGMPOptions
|
||||
}
|
||||
|
||||
// NewProtocolWithOptions returns an IPv4 network protocol.
|
||||
|
||||
@@ -8,6 +8,7 @@ go_library(
|
||||
"dhcpv6configurationfromndpra_string.go",
|
||||
"icmp.go",
|
||||
"ipv6.go",
|
||||
"mld.go",
|
||||
"ndp.go",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -19,6 +20,7 @@ go_library(
|
||||
"//pkg/tcpip/header/parse",
|
||||
"//pkg/tcpip/network/fragmentation",
|
||||
"//pkg/tcpip/network/hash",
|
||||
"//pkg/tcpip/network/ip",
|
||||
"//pkg/tcpip/stack",
|
||||
],
|
||||
)
|
||||
@@ -49,3 +51,16 @@ go_test(
|
||||
"@com_github_google_go_cmp//cmp:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "ipv6_x_test",
|
||||
size = "small",
|
||||
srcs = ["mld_test.go"],
|
||||
deps = [
|
||||
":ipv6",
|
||||
"//pkg/tcpip/checker",
|
||||
"//pkg/tcpip/header",
|
||||
"//pkg/tcpip/link/channel",
|
||||
"//pkg/tcpip/stack",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -163,7 +163,7 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool) {
|
||||
}
|
||||
|
||||
// TODO(b/112892170): Meaningfully handle all ICMP types.
|
||||
switch h.Type() {
|
||||
switch icmpType := h.Type(); icmpType {
|
||||
case header.ICMPv6PacketTooBig:
|
||||
received.PacketTooBig.Increment()
|
||||
hdr, ok := pkt.Data.PullUp(header.ICMPv6PacketTooBigMinimumSize)
|
||||
@@ -358,7 +358,7 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool) {
|
||||
pkt.TransportProtocolNumber = header.ICMPv6ProtocolNumber
|
||||
packet := header.ICMPv6(pkt.TransportHeader().Push(neighborAdvertSize))
|
||||
packet.SetType(header.ICMPv6NeighborAdvert)
|
||||
na := header.NDPNeighborAdvert(packet.NDPPayload())
|
||||
na := header.NDPNeighborAdvert(packet.MessageBody())
|
||||
|
||||
// As per RFC 4861 section 7.2.4:
|
||||
//
|
||||
@@ -644,8 +644,31 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool) {
|
||||
return
|
||||
}
|
||||
|
||||
case header.ICMPv6MulticastListenerQuery, header.ICMPv6MulticastListenerReport, header.ICMPv6MulticastListenerDone:
|
||||
var handler func(header.MLD)
|
||||
switch icmpType {
|
||||
case header.ICMPv6MulticastListenerQuery:
|
||||
received.MulticastListenerQuery.Increment()
|
||||
handler = e.mld.handleMulticastListenerQuery
|
||||
case header.ICMPv6MulticastListenerReport:
|
||||
received.MulticastListenerReport.Increment()
|
||||
handler = e.mld.handleMulticastListenerReport
|
||||
case header.ICMPv6MulticastListenerDone:
|
||||
received.MulticastListenerDone.Increment()
|
||||
default:
|
||||
panic(fmt.Sprintf("unrecognized MLD message = %d", icmpType))
|
||||
}
|
||||
if pkt.Data.Size()-header.ICMPv6HeaderSize < header.MLDMinimumSize {
|
||||
received.Invalid.Increment()
|
||||
return
|
||||
}
|
||||
|
||||
if handler != nil {
|
||||
handler(header.MLD(payload.ToView()))
|
||||
}
|
||||
|
||||
default:
|
||||
received.Invalid.Increment()
|
||||
received.Unrecognized.Increment()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -681,7 +704,7 @@ func (p *protocol) LinkAddressRequest(targetAddr, localAddr tcpip.Address, remot
|
||||
pkt.TransportProtocolNumber = header.ICMPv6ProtocolNumber
|
||||
packet := header.ICMPv6(pkt.TransportHeader().Push(neighborSolicitSize))
|
||||
packet.SetType(header.ICMPv6NeighborSolicit)
|
||||
ns := header.NDPNeighborSolicit(packet.NDPPayload())
|
||||
ns := header.NDPNeighborSolicit(packet.MessageBody())
|
||||
ns.SetTargetAddress(targetAddr)
|
||||
ns.Options().Serialize(optsSerializer)
|
||||
packet.SetChecksum(header.ICMPv6Checksum(packet, r.LocalAddress, r.RemoteAddress, buffer.VectorisedView{}))
|
||||
|
||||
@@ -271,6 +271,22 @@ func TestICMPCounts(t *testing.T) {
|
||||
typ: header.ICMPv6RedirectMsg,
|
||||
size: header.ICMPv6MinimumSize,
|
||||
},
|
||||
{
|
||||
typ: header.ICMPv6MulticastListenerQuery,
|
||||
size: header.MLDMinimumSize + header.ICMPv6HeaderSize,
|
||||
},
|
||||
{
|
||||
typ: header.ICMPv6MulticastListenerReport,
|
||||
size: header.MLDMinimumSize + header.ICMPv6HeaderSize,
|
||||
},
|
||||
{
|
||||
typ: header.ICMPv6MulticastListenerDone,
|
||||
size: header.MLDMinimumSize + header.ICMPv6HeaderSize,
|
||||
},
|
||||
{
|
||||
typ: 255, /* Unrecognized */
|
||||
size: 50,
|
||||
},
|
||||
}
|
||||
|
||||
handleIPv6Payload := func(icmp header.ICMPv6) {
|
||||
@@ -413,6 +429,22 @@ func TestICMPCountsWithNeighborCache(t *testing.T) {
|
||||
typ: header.ICMPv6RedirectMsg,
|
||||
size: header.ICMPv6MinimumSize,
|
||||
},
|
||||
{
|
||||
typ: header.ICMPv6MulticastListenerQuery,
|
||||
size: header.MLDMinimumSize + header.ICMPv6HeaderSize,
|
||||
},
|
||||
{
|
||||
typ: header.ICMPv6MulticastListenerReport,
|
||||
size: header.MLDMinimumSize + header.ICMPv6HeaderSize,
|
||||
},
|
||||
{
|
||||
typ: header.ICMPv6MulticastListenerDone,
|
||||
size: header.MLDMinimumSize + header.ICMPv6HeaderSize,
|
||||
},
|
||||
{
|
||||
typ: 255, /* Unrecognized */
|
||||
size: 50,
|
||||
},
|
||||
}
|
||||
|
||||
handleIPv6Payload := func(icmp header.ICMPv6) {
|
||||
@@ -1543,7 +1575,7 @@ func TestPacketQueing(t *testing.T) {
|
||||
hdr := buffer.NewPrependable(header.IPv6MinimumSize + naSize)
|
||||
pkt := header.ICMPv6(hdr.Prepend(naSize))
|
||||
pkt.SetType(header.ICMPv6NeighborAdvert)
|
||||
na := header.NDPNeighborAdvert(pkt.NDPPayload())
|
||||
na := header.NDPNeighborAdvert(pkt.MessageBody())
|
||||
na.SetSolicitedFlag(true)
|
||||
na.SetOverrideFlag(true)
|
||||
na.SetTargetAddress(host2IPv6Addr.AddressWithPrefix.Address)
|
||||
@@ -1592,7 +1624,7 @@ func TestCallsToNeighborCache(t *testing.T) {
|
||||
nsSize := header.ICMPv6NeighborSolicitMinimumSize + header.NDPLinkLayerAddressSize
|
||||
icmp := header.ICMPv6(buffer.NewView(nsSize))
|
||||
icmp.SetType(header.ICMPv6NeighborSolicit)
|
||||
ns := header.NDPNeighborSolicit(icmp.NDPPayload())
|
||||
ns := header.NDPNeighborSolicit(icmp.MessageBody())
|
||||
ns.SetTargetAddress(lladdr0)
|
||||
return icmp
|
||||
},
|
||||
@@ -1612,7 +1644,7 @@ func TestCallsToNeighborCache(t *testing.T) {
|
||||
nsSize := header.ICMPv6NeighborSolicitMinimumSize + header.NDPLinkLayerAddressSize
|
||||
icmp := header.ICMPv6(buffer.NewView(nsSize))
|
||||
icmp.SetType(header.ICMPv6NeighborSolicit)
|
||||
ns := header.NDPNeighborSolicit(icmp.NDPPayload())
|
||||
ns := header.NDPNeighborSolicit(icmp.MessageBody())
|
||||
ns.SetTargetAddress(lladdr0)
|
||||
ns.Options().Serialize(header.NDPOptionsSerializer{
|
||||
header.NDPSourceLinkLayerAddressOption(linkAddr1),
|
||||
@@ -1629,7 +1661,7 @@ func TestCallsToNeighborCache(t *testing.T) {
|
||||
nsSize := header.ICMPv6NeighborSolicitMinimumSize + header.NDPLinkLayerAddressSize
|
||||
icmp := header.ICMPv6(buffer.NewView(nsSize))
|
||||
icmp.SetType(header.ICMPv6NeighborSolicit)
|
||||
ns := header.NDPNeighborSolicit(icmp.NDPPayload())
|
||||
ns := header.NDPNeighborSolicit(icmp.MessageBody())
|
||||
ns.SetTargetAddress(lladdr0)
|
||||
return icmp
|
||||
},
|
||||
@@ -1645,7 +1677,7 @@ func TestCallsToNeighborCache(t *testing.T) {
|
||||
nsSize := header.ICMPv6NeighborSolicitMinimumSize + header.NDPLinkLayerAddressSize
|
||||
icmp := header.ICMPv6(buffer.NewView(nsSize))
|
||||
icmp.SetType(header.ICMPv6NeighborSolicit)
|
||||
ns := header.NDPNeighborSolicit(icmp.NDPPayload())
|
||||
ns := header.NDPNeighborSolicit(icmp.MessageBody())
|
||||
ns.SetTargetAddress(lladdr0)
|
||||
ns.Options().Serialize(header.NDPOptionsSerializer{
|
||||
header.NDPSourceLinkLayerAddressOption(linkAddr1),
|
||||
@@ -1662,7 +1694,7 @@ func TestCallsToNeighborCache(t *testing.T) {
|
||||
naSize := header.ICMPv6NeighborAdvertMinimumSize
|
||||
icmp := header.ICMPv6(buffer.NewView(naSize))
|
||||
icmp.SetType(header.ICMPv6NeighborAdvert)
|
||||
na := header.NDPNeighborAdvert(icmp.NDPPayload())
|
||||
na := header.NDPNeighborAdvert(icmp.MessageBody())
|
||||
na.SetSolicitedFlag(true)
|
||||
na.SetOverrideFlag(false)
|
||||
na.SetTargetAddress(lladdr1)
|
||||
@@ -1683,7 +1715,7 @@ func TestCallsToNeighborCache(t *testing.T) {
|
||||
naSize := header.ICMPv6NeighborAdvertMinimumSize + header.NDPLinkLayerAddressSize
|
||||
icmp := header.ICMPv6(buffer.NewView(naSize))
|
||||
icmp.SetType(header.ICMPv6NeighborAdvert)
|
||||
na := header.NDPNeighborAdvert(icmp.NDPPayload())
|
||||
na := header.NDPNeighborAdvert(icmp.MessageBody())
|
||||
na.SetSolicitedFlag(true)
|
||||
na.SetOverrideFlag(false)
|
||||
na.SetTargetAddress(lladdr1)
|
||||
@@ -1702,7 +1734,7 @@ func TestCallsToNeighborCache(t *testing.T) {
|
||||
naSize := header.ICMPv6NeighborAdvertMinimumSize + header.NDPLinkLayerAddressSize
|
||||
icmp := header.ICMPv6(buffer.NewView(naSize))
|
||||
icmp.SetType(header.ICMPv6NeighborAdvert)
|
||||
na := header.NDPNeighborAdvert(icmp.NDPPayload())
|
||||
na := header.NDPNeighborAdvert(icmp.MessageBody())
|
||||
na.SetSolicitedFlag(false)
|
||||
na.SetOverrideFlag(false)
|
||||
na.SetTargetAddress(lladdr1)
|
||||
@@ -1722,7 +1754,7 @@ func TestCallsToNeighborCache(t *testing.T) {
|
||||
naSize := header.ICMPv6NeighborAdvertMinimumSize + header.NDPLinkLayerAddressSize
|
||||
icmp := header.ICMPv6(buffer.NewView(naSize))
|
||||
icmp.SetType(header.ICMPv6NeighborAdvert)
|
||||
na := header.NDPNeighborAdvert(icmp.NDPPayload())
|
||||
na := header.NDPNeighborAdvert(icmp.MessageBody())
|
||||
na.SetSolicitedFlag(false)
|
||||
na.SetOverrideFlag(false)
|
||||
na.SetTargetAddress(lladdr1)
|
||||
|
||||
@@ -86,6 +86,8 @@ type endpoint struct {
|
||||
addressableEndpointState stack.AddressableEndpointState
|
||||
ndp ndpState
|
||||
}
|
||||
|
||||
mld mldState
|
||||
}
|
||||
|
||||
// NICNameFromID is a function that returns a stable name for the specified NIC,
|
||||
@@ -243,7 +245,7 @@ func (e *endpoint) Enable() *tcpip.Error {
|
||||
// (NDP NS) messages may be sent to the All-Nodes multicast group if the
|
||||
// source address of the NDP NS is the unspecified address, as per RFC 4861
|
||||
// section 7.2.4.
|
||||
if _, err := e.mu.addressableEndpointState.JoinGroup(header.IPv6AllNodesMulticastAddress); err != nil {
|
||||
if _, err := e.joinGroupLocked(header.IPv6AllNodesMulticastAddress); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -333,7 +335,7 @@ func (e *endpoint) disableLocked() {
|
||||
e.stopDADForPermanentAddressesLocked()
|
||||
|
||||
// The endpoint may have already left the multicast group.
|
||||
if _, err := e.mu.addressableEndpointState.LeaveGroup(header.IPv6AllNodesMulticastAddress); err != nil && err != tcpip.ErrBadLocalAddress {
|
||||
if _, err := e.leaveGroupLocked(header.IPv6AllNodesMulticastAddress); err != nil && err != tcpip.ErrBadLocalAddress {
|
||||
panic(fmt.Sprintf("unexpected error when leaving group = %s: %s", header.IPv6AllNodesMulticastAddress, err))
|
||||
}
|
||||
}
|
||||
@@ -378,7 +380,7 @@ func (e *endpoint) MaxHeaderLength() uint16 {
|
||||
return e.nic.MaxHeaderLength() + header.IPv6MinimumSize
|
||||
}
|
||||
|
||||
func (e *endpoint) addIPHeader(r *stack.Route, pkt *stack.PacketBuffer, params stack.NetworkHeaderParams) {
|
||||
func (e *endpoint) addIPHeader(srcAddr, dstAddr tcpip.Address, pkt *stack.PacketBuffer, params stack.NetworkHeaderParams) {
|
||||
length := uint16(pkt.Size())
|
||||
ip := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize))
|
||||
ip.Encode(&header.IPv6Fields{
|
||||
@@ -386,8 +388,8 @@ func (e *endpoint) addIPHeader(r *stack.Route, pkt *stack.PacketBuffer, params s
|
||||
NextHeader: uint8(params.Protocol),
|
||||
HopLimit: params.TTL,
|
||||
TrafficClass: params.TOS,
|
||||
SrcAddr: r.LocalAddress,
|
||||
DstAddr: r.RemoteAddress,
|
||||
SrcAddr: srcAddr,
|
||||
DstAddr: dstAddr,
|
||||
})
|
||||
pkt.NetworkProtocolNumber = ProtocolNumber
|
||||
}
|
||||
@@ -442,7 +444,7 @@ func (e *endpoint) handleFragments(r *stack.Route, gso *stack.GSO, networkMTU ui
|
||||
|
||||
// WritePacket writes a packet to the given destination address and protocol.
|
||||
func (e *endpoint) WritePacket(r *stack.Route, gso *stack.GSO, params stack.NetworkHeaderParams, pkt *stack.PacketBuffer) *tcpip.Error {
|
||||
e.addIPHeader(r, pkt, params)
|
||||
e.addIPHeader(r.LocalAddress, r.RemoteAddress, pkt, params)
|
||||
|
||||
// iptables filtering. All packets that reach here are locally
|
||||
// generated.
|
||||
@@ -531,7 +533,7 @@ func (e *endpoint) WritePackets(r *stack.Route, gso *stack.GSO, pkts stack.Packe
|
||||
|
||||
linkMTU := e.nic.MTU()
|
||||
for pb := pkts.Front(); pb != nil; pb = pb.Next() {
|
||||
e.addIPHeader(r, pb, params)
|
||||
e.addIPHeader(r.LocalAddress, r.RemoteAddress, pb, params)
|
||||
|
||||
networkMTU, err := calculateNetworkMTU(linkMTU, uint32(pb.NetworkHeader().View().Size()))
|
||||
if err != nil {
|
||||
@@ -1164,7 +1166,7 @@ func (e *endpoint) addAndAcquirePermanentAddressLocked(addr tcpip.AddressWithPre
|
||||
}
|
||||
|
||||
snmc := header.SolicitedNodeAddr(addr.Address)
|
||||
if _, err := e.mu.addressableEndpointState.JoinGroup(snmc); err != nil {
|
||||
if _, err := e.joinGroupLocked(snmc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1221,7 +1223,7 @@ func (e *endpoint) removePermanentEndpointLocked(addressEndpoint stack.AddressEn
|
||||
}
|
||||
|
||||
snmc := header.SolicitedNodeAddr(addr.Address)
|
||||
if _, err := e.mu.addressableEndpointState.LeaveGroup(snmc); err != nil && err != tcpip.ErrBadLocalAddress {
|
||||
if _, err := e.leaveGroupLocked(snmc); err != nil && err != tcpip.ErrBadLocalAddress {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1387,20 +1389,56 @@ func (e *endpoint) PermanentAddresses() []tcpip.AddressWithPrefix {
|
||||
|
||||
// JoinGroup implements stack.GroupAddressableEndpoint.
|
||||
func (e *endpoint) JoinGroup(addr tcpip.Address) (bool, *tcpip.Error) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
return e.joinGroupLocked(addr)
|
||||
}
|
||||
|
||||
// joinGroupLocked is like JoinGroup, but with locking requirements.
|
||||
//
|
||||
// Precondition: e.mu must be locked.
|
||||
func (e *endpoint) joinGroupLocked(addr tcpip.Address) (bool, *tcpip.Error) {
|
||||
if !header.IsV6MulticastAddress(addr) {
|
||||
return false, tcpip.ErrBadAddress
|
||||
}
|
||||
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
return e.mu.addressableEndpointState.JoinGroup(addr)
|
||||
// TODO(gvisor.dev/issue/4916): Keep track of join count and MLD state in a
|
||||
// single type.
|
||||
joined, err := e.mu.addressableEndpointState.JoinGroup(addr)
|
||||
if err != nil || !joined {
|
||||
return joined, err
|
||||
}
|
||||
|
||||
// joinGroup only returns an error if we try to join a group twice, but we
|
||||
// checked above to make sure that the group was newly joined.
|
||||
if err := e.mld.joinGroup(addr); err != nil {
|
||||
panic(fmt.Sprintf("e.mld.joinGroup(%s): %s", addr, err))
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// LeaveGroup implements stack.GroupAddressableEndpoint.
|
||||
func (e *endpoint) LeaveGroup(addr tcpip.Address) (bool, *tcpip.Error) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
return e.mu.addressableEndpointState.LeaveGroup(addr)
|
||||
return e.leaveGroupLocked(addr)
|
||||
}
|
||||
|
||||
// leaveGroupLocked is like LeaveGroup, but with locking requirements.
|
||||
//
|
||||
// Precondition: e.mu must be locked.
|
||||
func (e *endpoint) leaveGroupLocked(addr tcpip.Address) (bool, *tcpip.Error) {
|
||||
left, err := e.mu.addressableEndpointState.LeaveGroup(addr)
|
||||
if err != nil {
|
||||
return left, err
|
||||
}
|
||||
|
||||
if left {
|
||||
e.mld.leaveGroup(addr)
|
||||
}
|
||||
|
||||
return left, nil
|
||||
}
|
||||
|
||||
// IsInGroup implements stack.GroupAddressableEndpoint.
|
||||
@@ -1482,6 +1520,7 @@ func (p *protocol) NewEndpoint(nic stack.NetworkInterface, linkAddrCache stack.L
|
||||
slaacPrefixes: make(map[tcpip.Subnet]slaacPrefixState),
|
||||
}
|
||||
e.mu.ndp.initializeTempAddrState()
|
||||
e.mld.init(e, p.options.MLD)
|
||||
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
@@ -1638,6 +1677,9 @@ type Options struct {
|
||||
// seed that is too small would reduce randomness and increase predictability,
|
||||
// defeating the purpose of temporary SLAAC addresses.
|
||||
TempIIDSeed []byte
|
||||
|
||||
// MLD holds options for MLD.
|
||||
MLD MLDOptions
|
||||
}
|
||||
|
||||
// NewProtocolWithOptions returns an IPv6 network protocol.
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
// 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 ipv6
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
const (
|
||||
// UnsolicitedReportIntervalMax is the maximum delay between sending
|
||||
// unsolicited MLD reports.
|
||||
//
|
||||
// Obtained from RFC 2710 Section 7.10.
|
||||
UnsolicitedReportIntervalMax = 10 * time.Second
|
||||
)
|
||||
|
||||
// MLDOptions holds options for MLD.
|
||||
type MLDOptions struct {
|
||||
// Enabled indicates whether MLD will be performed.
|
||||
//
|
||||
// When enabled, MLD may transmit MLD report and done messages when
|
||||
// joining and leaving multicast groups respectively, and handle incoming
|
||||
// MLD packets.
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
var _ ip.MulticastGroupProtocol = (*mldState)(nil)
|
||||
|
||||
// mldState is the per-interface MLD state.
|
||||
//
|
||||
// mldState.init MUST be called to initialize the MLD state.
|
||||
type mldState struct {
|
||||
// The IPv6 endpoint this mldState is for.
|
||||
ep *endpoint
|
||||
opts MLDOptions
|
||||
|
||||
genericMulticastProtocol ip.GenericMulticastProtocolState
|
||||
}
|
||||
|
||||
// SendReport implements ip.MulticastGroupProtocol.
|
||||
func (mld *mldState) SendReport(groupAddress tcpip.Address) *tcpip.Error {
|
||||
return mld.writePacket(groupAddress, groupAddress, header.ICMPv6MulticastListenerReport)
|
||||
}
|
||||
|
||||
// SendLeave implements ip.MulticastGroupProtocol.
|
||||
func (mld *mldState) SendLeave(groupAddress tcpip.Address) *tcpip.Error {
|
||||
return mld.writePacket(header.IPv6AllRoutersMulticastAddress, groupAddress, header.ICMPv6MulticastListenerDone)
|
||||
}
|
||||
|
||||
// init sets up an mldState struct, and is required to be called before using
|
||||
// a new mldState.
|
||||
func (mld *mldState) init(ep *endpoint, opts MLDOptions) {
|
||||
mld.ep = ep
|
||||
mld.opts = opts
|
||||
mld.genericMulticastProtocol.Init(ep.protocol.stack.Rand(), ep.protocol.stack.Clock(), mld, UnsolicitedReportIntervalMax)
|
||||
}
|
||||
|
||||
func (mld *mldState) handleMulticastListenerQuery(mldHdr header.MLD) {
|
||||
if !mld.opts.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
mld.genericMulticastProtocol.HandleQuery(mldHdr.MulticastAddress(), mldHdr.MaximumResponseDelay())
|
||||
}
|
||||
|
||||
func (mld *mldState) handleMulticastListenerReport(mldHdr header.MLD) {
|
||||
if !mld.opts.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
mld.genericMulticastProtocol.HandleReport(mldHdr.MulticastAddress())
|
||||
}
|
||||
|
||||
// joinGroup handles joining a new group and sending and scheduling the required
|
||||
// messages.
|
||||
//
|
||||
// If the group is already joined, returns tcpip.ErrDuplicateAddress.
|
||||
func (mld *mldState) joinGroup(groupAddress tcpip.Address) *tcpip.Error {
|
||||
if !mld.opts.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
// As per RFC 2710 section 5 page 10,
|
||||
//
|
||||
// The link-scope all-nodes address (FF02::1) is handled as a special
|
||||
// case. The node starts in Idle Listener state for that address on
|
||||
// every interface, never transitions to another state, and never sends
|
||||
// a Report or Done for that address.
|
||||
//
|
||||
// This is equivalent to not performing MLD for the all-nodes multicast
|
||||
// address. Simply not performing MLD when the group is added will prevent
|
||||
// any work from being done on the all-nodes multicast group when leaving the
|
||||
// group or when query or report messages are received for it since the MGP
|
||||
// state will not know about it.
|
||||
if groupAddress == header.IPv6AllNodesMulticastAddress {
|
||||
return nil
|
||||
}
|
||||
|
||||
// JoinGroup returns false if we have already joined the group.
|
||||
if !mld.genericMulticastProtocol.JoinGroup(groupAddress) {
|
||||
return tcpip.ErrDuplicateAddress
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// leaveGroup handles removing the group from the membership map, cancels any
|
||||
// delay timers associated with that group, and sends the Done message, if
|
||||
// required.
|
||||
//
|
||||
// If the group is not joined, this function will do nothing.
|
||||
func (mld *mldState) leaveGroup(groupAddress tcpip.Address) {
|
||||
if !mld.opts.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
mld.genericMulticastProtocol.LeaveGroup(groupAddress)
|
||||
}
|
||||
|
||||
func (mld *mldState) writePacket(destAddress, groupAddress tcpip.Address, mldType header.ICMPv6Type) *tcpip.Error {
|
||||
sentStats := mld.ep.protocol.stack.Stats().ICMP.V6PacketsSent
|
||||
var mldStat *tcpip.StatCounter
|
||||
switch mldType {
|
||||
case header.ICMPv6MulticastListenerReport:
|
||||
mldStat = sentStats.MulticastListenerReport
|
||||
case header.ICMPv6MulticastListenerDone:
|
||||
mldStat = sentStats.MulticastListenerDone
|
||||
default:
|
||||
panic(fmt.Sprintf("unrecognized mld type = %d", mldType))
|
||||
}
|
||||
|
||||
icmp := header.ICMPv6(buffer.NewView(header.ICMPv6HeaderSize + header.MLDMinimumSize))
|
||||
icmp.SetType(mldType)
|
||||
header.MLD(icmp.MessageBody()).SetMulticastAddress(groupAddress)
|
||||
// TODO(gvisor.dev/issue/4888): We should not use the unspecified address,
|
||||
// rather we should select an appropriate local address.
|
||||
localAddress := header.IPv6Any
|
||||
icmp.SetChecksum(header.ICMPv6Checksum(icmp, localAddress, destAddress, buffer.VectorisedView{}))
|
||||
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
ReserveHeaderBytes: int(mld.ep.MaxHeaderLength()),
|
||||
Data: buffer.View(icmp).ToVectorisedView(),
|
||||
})
|
||||
|
||||
mld.ep.addIPHeader(localAddress, destAddress, pkt, stack.NetworkHeaderParams{
|
||||
Protocol: header.ICMPv6ProtocolNumber,
|
||||
TTL: header.MLDHopLimit,
|
||||
})
|
||||
// TODO(b/162198658): set the ROUTER_ALERT option when sending Host
|
||||
// Membership Reports.
|
||||
if err := mld.ep.nic.WritePacketToRemote(header.EthernetAddressFromMulticastIPv6Address(destAddress), nil /* gso */, ProtocolNumber, pkt); err != nil {
|
||||
sentStats.Dropped.Increment()
|
||||
return err
|
||||
}
|
||||
mldStat.Increment()
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// 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 ipv6_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip/checker"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
const (
|
||||
addr1 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
|
||||
)
|
||||
|
||||
func TestIPv6JoinLeaveSolicitedNodeAddressPerformsMLD(t *testing.T) {
|
||||
const nicID = 1
|
||||
|
||||
s := stack.New(stack.Options{
|
||||
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
|
||||
MLD: ipv6.MLDOptions{
|
||||
Enabled: true,
|
||||
},
|
||||
})},
|
||||
})
|
||||
e := channel.New(1, header.IPv6MinimumMTU, "")
|
||||
if err := s.CreateNIC(nicID, e); err != nil {
|
||||
t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
|
||||
}
|
||||
|
||||
// The stack will join an address's solicited node multicast address when
|
||||
// an address is added. An MLD report message should be sent for the
|
||||
// solicited-node group.
|
||||
if err := s.AddAddress(nicID, ipv6.ProtocolNumber, addr1); err != nil {
|
||||
t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ipv6.ProtocolNumber, addr1, err)
|
||||
}
|
||||
{
|
||||
p, ok := e.Read()
|
||||
if !ok {
|
||||
t.Fatal("expected a report message to be sent")
|
||||
}
|
||||
snmc := header.SolicitedNodeAddr(addr1)
|
||||
checker.IPv6(t, header.IPv6(stack.PayloadSince(p.Pkt.NetworkHeader())),
|
||||
checker.DstAddr(snmc),
|
||||
// Hop Limit for an MLD message must be 1 as per RFC 2710 section 3.
|
||||
checker.TTL(1),
|
||||
checker.MLD(header.ICMPv6MulticastListenerReport, header.MLDMinimumSize,
|
||||
checker.MLDMaxRespDelay(0),
|
||||
checker.MLDMulticastAddress(snmc),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// The stack will leave an address's solicited node multicast address when
|
||||
// an address is removed. An MLD done message should be sent for the
|
||||
// solicited-node group.
|
||||
if err := s.RemoveAddress(nicID, addr1); err != nil {
|
||||
t.Fatalf("RemoveAddress(%d, %s) = %s", nicID, addr1, err)
|
||||
}
|
||||
{
|
||||
p, ok := e.Read()
|
||||
if !ok {
|
||||
t.Fatal("expected a done message to be sent")
|
||||
}
|
||||
snmc := header.SolicitedNodeAddr(addr1)
|
||||
checker.IPv6(t, header.IPv6(stack.PayloadSince(p.Pkt.NetworkHeader())),
|
||||
checker.DstAddr(header.IPv6AllRoutersMulticastAddress),
|
||||
checker.TTL(1),
|
||||
checker.MLD(header.ICMPv6MulticastListenerDone, header.MLDMinimumSize,
|
||||
checker.MLDMaxRespDelay(0),
|
||||
checker.MLDMulticastAddress(snmc),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -773,7 +773,7 @@ func (ndp *ndpState) sendDADPacket(addr tcpip.Address, addressEndpoint stack.Add
|
||||
|
||||
icmpData := header.ICMPv6(buffer.NewView(header.ICMPv6NeighborSolicitMinimumSize))
|
||||
icmpData.SetType(header.ICMPv6NeighborSolicit)
|
||||
ns := header.NDPNeighborSolicit(icmpData.NDPPayload())
|
||||
ns := header.NDPNeighborSolicit(icmpData.MessageBody())
|
||||
ns.SetTargetAddress(addr)
|
||||
icmpData.SetChecksum(header.ICMPv6Checksum(icmpData, r.LocalAddress, r.RemoteAddress, buffer.VectorisedView{}))
|
||||
|
||||
@@ -1944,7 +1944,7 @@ func (ndp *ndpState) startSolicitingRouters() {
|
||||
payloadSize := header.ICMPv6HeaderSize + header.NDPRSMinimumSize + int(optsSerializer.Length())
|
||||
icmpData := header.ICMPv6(buffer.NewView(payloadSize))
|
||||
icmpData.SetType(header.ICMPv6RouterSolicit)
|
||||
rs := header.NDPRouterSolicit(icmpData.NDPPayload())
|
||||
rs := header.NDPRouterSolicit(icmpData.MessageBody())
|
||||
rs.Options().Serialize(optsSerializer)
|
||||
icmpData.SetChecksum(header.ICMPv6Checksum(icmpData, r.LocalAddress, r.RemoteAddress, buffer.VectorisedView{}))
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@ func TestNeighorSolicitationWithSourceLinkLayerOption(t *testing.T) {
|
||||
hdr := buffer.NewPrependable(header.IPv6MinimumSize + ndpNSSize)
|
||||
pkt := header.ICMPv6(hdr.Prepend(ndpNSSize))
|
||||
pkt.SetType(header.ICMPv6NeighborSolicit)
|
||||
ns := header.NDPNeighborSolicit(pkt.NDPPayload())
|
||||
ns := header.NDPNeighborSolicit(pkt.MessageBody())
|
||||
ns.SetTargetAddress(lladdr0)
|
||||
opts := ns.Options()
|
||||
copy(opts, test.optsBuf)
|
||||
@@ -311,7 +311,7 @@ func TestNeighorSolicitationWithSourceLinkLayerOptionUsingNeighborCache(t *testi
|
||||
hdr := buffer.NewPrependable(header.IPv6MinimumSize + ndpNSSize)
|
||||
pkt := header.ICMPv6(hdr.Prepend(ndpNSSize))
|
||||
pkt.SetType(header.ICMPv6NeighborSolicit)
|
||||
ns := header.NDPNeighborSolicit(pkt.NDPPayload())
|
||||
ns := header.NDPNeighborSolicit(pkt.MessageBody())
|
||||
ns.SetTargetAddress(lladdr0)
|
||||
opts := ns.Options()
|
||||
copy(opts, test.optsBuf)
|
||||
@@ -591,7 +591,7 @@ func TestNeighorSolicitationResponse(t *testing.T) {
|
||||
hdr := buffer.NewPrependable(header.IPv6MinimumSize + ndpNSSize)
|
||||
pkt := header.ICMPv6(hdr.Prepend(ndpNSSize))
|
||||
pkt.SetType(header.ICMPv6NeighborSolicit)
|
||||
ns := header.NDPNeighborSolicit(pkt.NDPPayload())
|
||||
ns := header.NDPNeighborSolicit(pkt.MessageBody())
|
||||
ns.SetTargetAddress(nicAddr)
|
||||
opts := ns.Options()
|
||||
opts.Serialize(test.nsOpts)
|
||||
@@ -672,7 +672,7 @@ func TestNeighorSolicitationResponse(t *testing.T) {
|
||||
hdr := buffer.NewPrependable(header.IPv6MinimumSize + ndpNASize)
|
||||
pkt := header.ICMPv6(hdr.Prepend(ndpNASize))
|
||||
pkt.SetType(header.ICMPv6NeighborAdvert)
|
||||
na := header.NDPNeighborAdvert(pkt.NDPPayload())
|
||||
na := header.NDPNeighborAdvert(pkt.MessageBody())
|
||||
na.SetSolicitedFlag(true)
|
||||
na.SetOverrideFlag(true)
|
||||
na.SetTargetAddress(test.nsSrc)
|
||||
@@ -777,7 +777,7 @@ func TestNeighorAdvertisementWithTargetLinkLayerOption(t *testing.T) {
|
||||
hdr := buffer.NewPrependable(header.IPv6MinimumSize + ndpNASize)
|
||||
pkt := header.ICMPv6(hdr.Prepend(ndpNASize))
|
||||
pkt.SetType(header.ICMPv6NeighborAdvert)
|
||||
ns := header.NDPNeighborAdvert(pkt.NDPPayload())
|
||||
ns := header.NDPNeighborAdvert(pkt.MessageBody())
|
||||
ns.SetTargetAddress(lladdr1)
|
||||
opts := ns.Options()
|
||||
copy(opts, test.optsBuf)
|
||||
@@ -890,7 +890,7 @@ func TestNeighorAdvertisementWithTargetLinkLayerOptionUsingNeighborCache(t *test
|
||||
hdr := buffer.NewPrependable(header.IPv6MinimumSize + ndpNASize)
|
||||
pkt := header.ICMPv6(hdr.Prepend(ndpNASize))
|
||||
pkt.SetType(header.ICMPv6NeighborAdvert)
|
||||
ns := header.NDPNeighborAdvert(pkt.NDPPayload())
|
||||
ns := header.NDPNeighborAdvert(pkt.MessageBody())
|
||||
ns.SetTargetAddress(lladdr1)
|
||||
opts := ns.Options()
|
||||
copy(opts, test.optsBuf)
|
||||
@@ -1346,7 +1346,7 @@ func TestRouterAdvertValidation(t *testing.T) {
|
||||
pkt := header.ICMPv6(hdr.Prepend(icmpSize))
|
||||
pkt.SetType(header.ICMPv6RouterAdvert)
|
||||
pkt.SetCode(test.code)
|
||||
copy(pkt.NDPPayload(), test.ndpPayload)
|
||||
copy(pkt.MessageBody(), test.ndpPayload)
|
||||
payloadLength := hdr.UsedLength()
|
||||
pkt.SetChecksum(header.ICMPv6Checksum(pkt, test.src, header.IPv6AllNodesMulticastAddress, buffer.VectorisedView{}))
|
||||
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -569,7 +569,7 @@ func rxNDPSolicit(e *channel.Endpoint, tgt tcpip.Address) {
|
||||
hdr := buffer.NewPrependable(header.IPv6MinimumSize + header.ICMPv6NeighborSolicitMinimumSize)
|
||||
pkt := header.ICMPv6(hdr.Prepend(header.ICMPv6NeighborSolicitMinimumSize))
|
||||
pkt.SetType(header.ICMPv6NeighborSolicit)
|
||||
ns := header.NDPNeighborSolicit(pkt.NDPPayload())
|
||||
ns := header.NDPNeighborSolicit(pkt.MessageBody())
|
||||
ns.SetTargetAddress(tgt)
|
||||
snmc := header.SolicitedNodeAddr(tgt)
|
||||
pkt.SetChecksum(header.ICMPv6Checksum(pkt, header.IPv6Any, snmc, buffer.VectorisedView{}))
|
||||
@@ -611,7 +611,7 @@ func TestDADFail(t *testing.T) {
|
||||
hdr := buffer.NewPrependable(header.IPv6MinimumSize + naSize)
|
||||
pkt := header.ICMPv6(hdr.Prepend(naSize))
|
||||
pkt.SetType(header.ICMPv6NeighborAdvert)
|
||||
na := header.NDPNeighborAdvert(pkt.NDPPayload())
|
||||
na := header.NDPNeighborAdvert(pkt.MessageBody())
|
||||
na.SetSolicitedFlag(true)
|
||||
na.SetOverrideFlag(true)
|
||||
na.SetTargetAddress(tgt)
|
||||
@@ -988,7 +988,7 @@ func raBufWithOptsAndDHCPv6(ip tcpip.Address, rl uint16, managedAddress, otherCo
|
||||
pkt := header.ICMPv6(hdr.Prepend(icmpSize))
|
||||
pkt.SetType(header.ICMPv6RouterAdvert)
|
||||
pkt.SetCode(0)
|
||||
raPayload := pkt.NDPPayload()
|
||||
raPayload := pkt.MessageBody()
|
||||
ra := header.NDPRouterAdvert(raPayload)
|
||||
// Populate the Router Lifetime.
|
||||
binary.BigEndian.PutUint16(raPayload[2:], rl)
|
||||
|
||||
@@ -1373,6 +1373,18 @@ type ICMPv6PacketStats struct {
|
||||
// RedirectMsg is the total number of ICMPv6 redirect message packets
|
||||
// counted.
|
||||
RedirectMsg *StatCounter
|
||||
|
||||
// MulticastListenerQuery is the total number of Multicast Listener Query
|
||||
// messages counted.
|
||||
MulticastListenerQuery *StatCounter
|
||||
|
||||
// MulticastListenerReport is the total number of Multicast Listener Report
|
||||
// messages counted.
|
||||
MulticastListenerReport *StatCounter
|
||||
|
||||
// MulticastListenerDone is the total number of Multicast Listener Done
|
||||
// messages counted.
|
||||
MulticastListenerDone *StatCounter
|
||||
}
|
||||
|
||||
// ICMPv4SentPacketStats collects outbound ICMPv4-specific stats.
|
||||
@@ -1414,6 +1426,10 @@ type ICMPv6SentPacketStats struct {
|
||||
type ICMPv6ReceivedPacketStats struct {
|
||||
ICMPv6PacketStats
|
||||
|
||||
// Unrecognized is the total number of ICMPv6 packets received that the
|
||||
// transport layer does not know how to parse.
|
||||
Unrecognized *StatCounter
|
||||
|
||||
// Invalid is the total number of ICMPv6 packets received that the
|
||||
// transport layer could not parse.
|
||||
Invalid *StatCounter
|
||||
|
||||
@@ -830,7 +830,9 @@ func (l *ICMPv6) ToBytes() ([]byte, error) {
|
||||
if l.Code != nil {
|
||||
h.SetCode(*l.Code)
|
||||
}
|
||||
copy(h.NDPPayload(), l.Payload)
|
||||
if n := copy(h.MessageBody(), l.Payload); n != len(l.Payload) {
|
||||
panic(fmt.Sprintf("copied %d bytes, expected to copy %d bytes", n, len(l.Payload)))
|
||||
}
|
||||
if l.Checksum != nil {
|
||||
h.SetChecksum(*l.Checksum)
|
||||
} else {
|
||||
@@ -876,7 +878,7 @@ func parseICMPv6(b []byte) (Layer, layerParser) {
|
||||
Type: ICMPv6Type(h.Type()),
|
||||
Code: ICMPv6Code(h.Code()),
|
||||
Checksum: Uint16(h.Checksum()),
|
||||
Payload: h.NDPPayload(),
|
||||
Payload: h.MessageBody(),
|
||||
}
|
||||
return &icmpv6, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user