Do not perform MLD for certain multicast scopes

...as per RFC 2710 section 5 page 10.

Test: ipv6_test.TestMLDSkipProtocol
PiperOrigin-RevId: 367031126
This commit is contained in:
Ghanan Gowripalan
2021-04-06 10:15:46 -07:00
committed by gVisor bot
parent fb4c700d06
commit d7fd00bad1
7 changed files with 313 additions and 70 deletions
+43 -6
View File
@@ -142,11 +142,6 @@ const (
// ipv6MulticastAddressScopeMask is the mask for the scope (scop) field,
// within the byte holding the field, as per RFC 4291 section 2.7.
ipv6MulticastAddressScopeMask = 0xF
// ipv6LinkLocalMulticastScope is the value of the scope (scop) field within
// a multicast IPv6 address that indicates the address has link-local scope,
// as per RFC 4291 section 2.7.
ipv6LinkLocalMulticastScope = 2
)
// IPv6EmptySubnet is the empty IPv6 subnet. It may also be known as the
@@ -399,7 +394,7 @@ func IsV6LoopbackAddress(addr tcpip.Address) bool {
// IsV6LinkLocalMulticastAddress determines if the provided address is an IPv6
// link-local multicast address.
func IsV6LinkLocalMulticastAddress(addr tcpip.Address) bool {
return IsV6MulticastAddress(addr) && addr[ipv6MulticastAddressScopeByteIdx]&ipv6MulticastAddressScopeMask == ipv6LinkLocalMulticastScope
return IsV6MulticastAddress(addr) && V6MulticastScope(addr) == IPv6LinkLocalMulticastScope
}
// AppendOpaqueInterfaceIdentifier appends a 64 bit opaque interface identifier
@@ -520,3 +515,45 @@ func GenerateTempIPv6SLAACAddr(tempIIDHistory []byte, stableAddr tcpip.Address)
PrefixLen: IIDOffsetInIPv6Address * 8,
}
}
// IPv6MulticastScope is the scope of a multicast IPv6 address.
type IPv6MulticastScope uint8
// The various values for IPv6 multicast scopes, as per RFC 7346 section 2:
//
// +------+--------------------------+-------------------------+
// | scop | NAME | REFERENCE |
// +------+--------------------------+-------------------------+
// | 0 | Reserved | [RFC4291], RFC 7346 |
// | 1 | Interface-Local scope | [RFC4291], RFC 7346 |
// | 2 | Link-Local scope | [RFC4291], RFC 7346 |
// | 3 | Realm-Local scope | [RFC4291], RFC 7346 |
// | 4 | Admin-Local scope | [RFC4291], RFC 7346 |
// | 5 | Site-Local scope | [RFC4291], RFC 7346 |
// | 6 | Unassigned | |
// | 7 | Unassigned | |
// | 8 | Organization-Local scope | [RFC4291], RFC 7346 |
// | 9 | Unassigned | |
// | A | Unassigned | |
// | B | Unassigned | |
// | C | Unassigned | |
// | D | Unassigned | |
// | E | Global scope | [RFC4291], RFC 7346 |
// | F | Reserved | [RFC4291], RFC 7346 |
// +------+--------------------------+-------------------------+
const (
IPv6Reserved0MulticastScope = IPv6MulticastScope(0x0)
IPv6InterfaceLocalMulticastScope = IPv6MulticastScope(0x1)
IPv6LinkLocalMulticastScope = IPv6MulticastScope(0x2)
IPv6RealmLocalMulticastScope = IPv6MulticastScope(0x3)
IPv6AdminLocalMulticastScope = IPv6MulticastScope(0x4)
IPv6SiteLocalMulticastScope = IPv6MulticastScope(0x5)
IPv6OrganizationLocalMulticastScope = IPv6MulticastScope(0x8)
IPv6GlobalMulticastScope = IPv6MulticastScope(0xE)
IPv6ReservedFMulticastScope = IPv6MulticastScope(0xF)
)
// V6MulticastScope returns the scope of a multicast address.
func V6MulticastScope(addr tcpip.Address) IPv6MulticastScope {
return IPv6MulticastScope(addr[ipv6MulticastAddressScopeByteIdx] & ipv6MulticastAddressScopeMask)
}
+80
View File
@@ -373,3 +373,83 @@ func TestSolicitedNodeAddr(t *testing.T) {
})
}
}
func TestV6MulticastScope(t *testing.T) {
tests := []struct {
addr tcpip.Address
want header.IPv6MulticastScope
}{
{
addr: "\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
want: header.IPv6Reserved0MulticastScope,
},
{
addr: "\xff\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
want: header.IPv6InterfaceLocalMulticastScope,
},
{
addr: "\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
want: header.IPv6LinkLocalMulticastScope,
},
{
addr: "\xff\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
want: header.IPv6RealmLocalMulticastScope,
},
{
addr: "\xff\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
want: header.IPv6AdminLocalMulticastScope,
},
{
addr: "\xff\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
want: header.IPv6SiteLocalMulticastScope,
},
{
addr: "\xff\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
want: header.IPv6MulticastScope(6),
},
{
addr: "\xff\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
want: header.IPv6MulticastScope(7),
},
{
addr: "\xff\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
want: header.IPv6OrganizationLocalMulticastScope,
},
{
addr: "\xff\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
want: header.IPv6MulticastScope(9),
},
{
addr: "\xff\x0a\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
want: header.IPv6MulticastScope(10),
},
{
addr: "\xff\x0b\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
want: header.IPv6MulticastScope(11),
},
{
addr: "\xff\x0c\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
want: header.IPv6MulticastScope(12),
},
{
addr: "\xff\x0d\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
want: header.IPv6MulticastScope(13),
},
{
addr: "\xff\x0e\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
want: header.IPv6GlobalMulticastScope,
},
{
addr: "\xff\x0f\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
want: header.IPv6ReservedFMulticastScope,
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%s", test.addr), func(t *testing.T) {
if got := header.V6MulticastScope(test.addr); got != test.want {
t.Fatalf("got header.V6MulticastScope(%s) = %d, want = %d", test.addr, got, test.want)
}
})
}
}
@@ -156,14 +156,6 @@ type GenericMulticastProtocolOptions struct {
//
// Unsolicited reports are transmitted when a group is newly joined.
MaxUnsolicitedReportDelay time.Duration
// AllNodesAddress is a multicast address that all nodes on a network should
// be a member of.
//
// This address will not have the generic multicast protocol performed on it;
// it will be left in the non member/listener state, and packets will never
// be sent for it.
AllNodesAddress tcpip.Address
}
// MulticastGroupProtocol is a multicast group protocol whose core state machine
@@ -188,6 +180,10 @@ type MulticastGroupProtocol interface {
// SendLeave sends a multicast leave for the specified group address.
SendLeave(groupAddress tcpip.Address) tcpip.Error
// ShouldPerformProtocol returns true iff the protocol should be performed for
// the specified group.
ShouldPerformProtocol(tcpip.Address) bool
}
// GenericMulticastProtocolState is the per interface generic multicast protocol
@@ -455,20 +451,7 @@ func (g *GenericMulticastProtocolState) initializeNewMemberLocked(groupAddress t
info.lastToSendReport = false
if groupAddress == g.opts.AllNodesAddress {
// As per RFC 2236 section 6 page 10 (for IGMPv2),
//
// 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.
//
// As per RFC 2710 section 5 page 10 (for MLDv1),
//
// 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.
if !g.opts.Protocol.ShouldPerformProtocol(groupAddress) {
info.state = idleMember
return
}
@@ -537,20 +520,7 @@ func (g *GenericMulticastProtocolState) maybeSendLeave(groupAddress tcpip.Addres
return
}
if groupAddress == g.opts.AllNodesAddress {
// As per RFC 2236 section 6 page 10 (for IGMPv2),
//
// 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.
//
// As per RFC 2710 section 5 page 10 (for MLDv1),
//
// 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.
if !g.opts.Protocol.ShouldPerformProtocol(groupAddress) {
return
}
@@ -627,20 +597,7 @@ func (g *GenericMulticastProtocolState) setDelayTimerForAddressRLocked(groupAddr
return
}
if groupAddress == g.opts.AllNodesAddress {
// As per RFC 2236 section 6 page 10 (for IGMPv2),
//
// 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.
//
// As per RFC 2710 section 5 page 10 (for MLDv1),
//
// 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.
if !g.opts.Protocol.ShouldPerformProtocol(groupAddress) {
return
}
@@ -43,6 +43,8 @@ type mockMulticastGroupProtocolProtectedFields struct {
type mockMulticastGroupProtocol struct {
t *testing.T
skipProtocolAddress tcpip.Address
mu mockMulticastGroupProtocolProtectedFields
}
@@ -165,6 +167,11 @@ func (m *mockMulticastGroupProtocol) SendLeave(groupAddress tcpip.Address) tcpip
return nil
}
// ShouldPerformProtocol implements ip.MulticastGroupProtocol.
func (m *mockMulticastGroupProtocol) ShouldPerformProtocol(groupAddress tcpip.Address) bool {
return groupAddress != m.skipProtocolAddress
}
func (m *mockMulticastGroupProtocol) check(sendReportGroupAddresses []tcpip.Address, sendLeaveGroupAddresses []tcpip.Address) string {
m.mu.Lock()
defer m.mu.Unlock()
@@ -193,10 +200,11 @@ func (m *mockMulticastGroupProtocol) check(sendReportGroupAddresses []tcpip.Addr
cmp.FilterPath(
func(p cmp.Path) bool {
switch p.Last().String() {
case ".RWMutex", ".t", ".makeQueuePackets", ".disabled", ".genericMulticastGroup":
case ".RWMutex", ".t", ".makeQueuePackets", ".disabled", ".genericMulticastGroup", ".skipProtocolAddress":
return true
default:
return false
}
return false
},
cmp.Ignore(),
),
@@ -225,14 +233,13 @@ func TestJoinGroup(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mgp := mockMulticastGroupProtocol{t: t}
mgp := mockMulticastGroupProtocol{t: t, skipProtocolAddress: addr2}
clock := faketime.NewManualClock()
mgp.init(ip.GenericMulticastProtocolOptions{
Rand: rand.New(rand.NewSource(0)),
Clock: clock,
MaxUnsolicitedReportDelay: maxUnsolicitedReportDelay,
AllNodesAddress: addr2,
})
// Joining a group should send a report immediately and another after
@@ -279,14 +286,13 @@ func TestLeaveGroup(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mgp := mockMulticastGroupProtocol{t: t}
mgp := mockMulticastGroupProtocol{t: t, skipProtocolAddress: addr2}
clock := faketime.NewManualClock()
mgp.init(ip.GenericMulticastProtocolOptions{
Rand: rand.New(rand.NewSource(1)),
Clock: clock,
MaxUnsolicitedReportDelay: maxUnsolicitedReportDelay,
AllNodesAddress: addr2,
})
mgp.joinGroup(test.addr)
@@ -356,14 +362,13 @@ func TestHandleReport(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mgp := mockMulticastGroupProtocol{t: t}
mgp := mockMulticastGroupProtocol{t: t, skipProtocolAddress: addr3}
clock := faketime.NewManualClock()
mgp.init(ip.GenericMulticastProtocolOptions{
Rand: rand.New(rand.NewSource(2)),
Clock: clock,
MaxUnsolicitedReportDelay: maxUnsolicitedReportDelay,
AllNodesAddress: addr3,
})
mgp.joinGroup(addr1)
@@ -446,14 +451,13 @@ func TestHandleQuery(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mgp := mockMulticastGroupProtocol{t: t}
mgp := mockMulticastGroupProtocol{t: t, skipProtocolAddress: addr3}
clock := faketime.NewManualClock()
mgp.init(ip.GenericMulticastProtocolOptions{
Rand: rand.New(rand.NewSource(3)),
Clock: clock,
MaxUnsolicitedReportDelay: maxUnsolicitedReportDelay,
AllNodesAddress: addr3,
})
mgp.joinGroup(addr1)
@@ -574,14 +578,13 @@ func TestJoinCount(t *testing.T) {
}
func TestMakeAllNonMemberAndInitialize(t *testing.T) {
mgp := mockMulticastGroupProtocol{t: t}
mgp := mockMulticastGroupProtocol{t: t, skipProtocolAddress: addr3}
clock := faketime.NewManualClock()
mgp.init(ip.GenericMulticastProtocolOptions{
Rand: rand.New(rand.NewSource(3)),
Clock: clock,
MaxUnsolicitedReportDelay: maxUnsolicitedReportDelay,
AllNodesAddress: addr3,
})
mgp.joinGroup(addr1)
+11 -1
View File
@@ -126,6 +126,17 @@ func (igmp *igmpState) SendLeave(groupAddress tcpip.Address) tcpip.Error {
return err
}
// ShouldPerformProtocol implements ip.MulticastGroupProtocol.
func (igmp *igmpState) ShouldPerformProtocol(groupAddress tcpip.Address) bool {
// 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.
return groupAddress != header.IPv4AllSystems
}
// init sets up an igmpState struct, and is required to be called before using
// a new igmpState.
//
@@ -137,7 +148,6 @@ func (igmp *igmpState) init(ep *endpoint) {
Clock: ep.protocol.stack.Clock(),
Protocol: igmp,
MaxUnsolicitedReportDelay: UnsolicitedReportIntervalMax,
AllNodesAddress: header.IPv4AllSystems,
})
igmp.igmpV1Present = igmpV1PresentDefault
igmp.igmpV1Job = ep.protocol.stack.NewJob(&ep.mu, func() {
+19 -1
View File
@@ -80,6 +80,25 @@ func (mld *mldState) SendLeave(groupAddress tcpip.Address) tcpip.Error {
return err
}
// ShouldPerformProtocol implements ip.MulticastGroupProtocol.
func (mld *mldState) ShouldPerformProtocol(groupAddress tcpip.Address) bool {
// 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.
//
// MLD messages are never sent for multicast addresses whose scope is 0
// (reserved) or 1 (node-local).
if groupAddress == header.IPv6AllNodesMulticastAddress {
return false
}
scope := header.V6MulticastScope(groupAddress)
return scope != header.IPv6Reserved0MulticastScope && scope != header.IPv6InterfaceLocalMulticastScope
}
// init sets up an mldState struct, and is required to be called before using
// a new mldState.
//
@@ -91,7 +110,6 @@ func (mld *mldState) init(ep *endpoint) {
Clock: ep.protocol.stack.Clock(),
Protocol: mld,
MaxUnsolicitedReportDelay: UnsolicitedReportIntervalMax,
AllNodesAddress: header.IPv6AllNodesMulticastAddress,
})
}
+138
View File
@@ -464,3 +464,141 @@ func TestMLDPacketValidation(t *testing.T) {
})
}
}
func TestMLDSkipProtocol(t *testing.T) {
const nicID = 1
tests := []struct {
name string
group tcpip.Address
expectReport bool
}{
{
name: "Reserverd0",
group: "\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: false,
},
{
name: "Interface Local",
group: "\xff\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: false,
},
{
name: "Link Local",
group: "\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Realm Local",
group: "\xff\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Admin Local",
group: "\xff\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Site Local",
group: "\xff\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Unassigned(6)",
group: "\xff\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Unassigned(7)",
group: "\xff\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Organization Local",
group: "\xff\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Unassigned(9)",
group: "\xff\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Unassigned(A)",
group: "\xff\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Unassigned(B)",
group: "\xff\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Unassigned(C)",
group: "\xff\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Unassigned(D)",
group: "\xff\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Global",
group: "\xff\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "ReservedF",
group: "\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
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)
}
if err := s.AddAddress(nicID, ipv6.ProtocolNumber, linkLocalAddr); err != nil {
t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ipv6.ProtocolNumber, linkLocalAddr, err)
}
if p, ok := e.Read(); !ok {
t.Fatal("expected a report message to be sent")
} else {
validateMLDPacket(t, stack.PayloadSince(p.Pkt.NetworkHeader()), linkLocalAddr, linkLocalAddrSNMC, header.ICMPv6MulticastListenerReport, linkLocalAddrSNMC)
}
if err := s.JoinGroup(ipv6.ProtocolNumber, nicID, test.group); err != nil {
t.Fatalf("s.JoinGroup(%d, %d, %s): %s", ipv6.ProtocolNumber, nicID, test.group, err)
}
if isInGroup, err := s.IsInGroup(nicID, test.group); err != nil {
t.Fatalf("IsInGroup(%d, %s): %s", nicID, test.group, err)
} else if !isInGroup {
t.Fatalf("got IsInGroup(%d, %s) = false, want = true", nicID, test.group)
}
if !test.expectReport {
if p, ok := e.Read(); ok {
t.Fatalf("got e.Read() = (%#v, true), want = (_, false)", p)
}
return
}
if p, ok := e.Read(); !ok {
t.Fatal("expected a report message to be sent")
} else {
validateMLDPacket(t, stack.PayloadSince(p.Pkt.NetworkHeader()), linkLocalAddr, test.group, header.ICMPv6MulticastListenerReport, test.group)
}
})
}
}