diff options
author | gVisor bot <gvisor-bot@google.com> | 2020-11-26 02:03:52 +0000 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2020-11-26 02:03:52 +0000 |
commit | 3927c666bb886f7aceb0c6d369a0fda2f14d6616 (patch) | |
tree | 318bf857949e11d1764914418f7a23bd7f102a3b /pkg/tcpip/network/ipv4 | |
parent | 2442e44c4b5642796de0687d024fbaf77178cb4e (diff) | |
parent | bc81fccedae4c66e61a6b62eac44dd11fae413ac (diff) |
Merge release-20201109.0-118-gbc81fcced (automated)
Diffstat (limited to 'pkg/tcpip/network/ipv4')
-rw-r--r-- | pkg/tcpip/network/ipv4/igmp.go | 57 | ||||
-rw-r--r-- | pkg/tcpip/network/ipv4/ipv4.go | 85 |
2 files changed, 101 insertions, 41 deletions
diff --git a/pkg/tcpip/network/ipv4/igmp.go b/pkg/tcpip/network/ipv4/igmp.go index c9bf117de..37f1822ca 100644 --- a/pkg/tcpip/network/ipv4/igmp.go +++ b/pkg/tcpip/network/ipv4/igmp.go @@ -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) diff --git a/pkg/tcpip/network/ipv4/ipv4.go b/pkg/tcpip/network/ipv4/ipv4.go index 7c759be9a..be9c8e2f9 100644 --- a/pkg/tcpip/network/ipv4/ipv4.go +++ b/pkg/tcpip/network/ipv4/ipv4.go @@ -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 } + // 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 + } - 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) + // 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 joinedGroup, 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) +} + +// 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 + } - leftGroup, err := e.mu.addressableEndpointState.LeaveGroup(addr) - if err == nil && leftGroup && e.protocol.options.IGMPEnabled { + 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. |