Validate IGMP packets

This change also adds support for Router Alert option processing on
incoming packets, a new stat for Router Alert option, and exports
all the IP-option related stats.

Fixes #5491

PiperOrigin-RevId: 358238123
This commit is contained in:
Arthur Sfez
2021-02-18 12:29:54 -08:00
committed by gVisor bot
parent 26eada5dea
commit 2a2cb29e1c
9 changed files with 404 additions and 106 deletions
+4
View File
@@ -184,6 +184,10 @@ var Metrics = tcpip.Stats{
IPTablesPreroutingDropped: mustCreateMetric("/netstack/ip/iptables/prerouting_dropped", "Total number of IP packets dropped in the Prerouting chain."),
IPTablesInputDropped: mustCreateMetric("/netstack/ip/iptables/input_dropped", "Total number of IP packets dropped in the Input chain."),
IPTablesOutputDropped: mustCreateMetric("/netstack/ip/iptables/output_dropped", "Total number of IP packets dropped in the Output chain."),
OptionTimestampReceived: mustCreateMetric("/netstack/ip/options/timestamp_received", "Total number of timestamp options found in received IP packets."),
OptionRecordRouteReceived: mustCreateMetric("/netstack/ip/options/record_route_received", "Total number of record route options found in received IP packets."),
OptionRouterAlertReceived: mustCreateMetric("/netstack/ip/options/router_alert_received", "Total number of router alert options found in received IP packets."),
OptionUnknownReceived: mustCreateMetric("/netstack/ip/options/unknown_received", "Total number of unknown options found in received IP packets."),
},
ARP: tcpip.ARPStats{
PacketsReceived: mustCreateMetric("/netstack/arp/packets_received", "Number of ARP packets received from the link layer."),
+43 -3
View File
@@ -519,6 +519,7 @@ func (o *IPv4OptionGeneric) Contents() []byte { return []byte(*o) }
// IPv4OptionIterator is an iterator pointing to a specific IP option
// at any point of time. It also holds information as to a new options buffer
// that we are building up to hand back to the caller.
// TODO(https://gvisor.dev/issues/5513): Add unit tests for IPv4OptionIterator.
type IPv4OptionIterator struct {
options IPv4Options
// ErrCursor is where we are while parsing options. It is exported as any
@@ -539,6 +540,15 @@ func (o IPv4Options) MakeIterator() IPv4OptionIterator {
}
}
// InitReplacement copies the option into the new option buffer.
func (i *IPv4OptionIterator) InitReplacement(option IPv4Option) IPv4Options {
replacementOption := i.RemainingBuffer()[:option.Size()]
if copied := copy(replacementOption, option.Contents()); copied != len(replacementOption) {
panic(fmt.Sprintf("copied %d bytes in the replacement option buffer, expected %d bytes", copied, len(replacementOption)))
}
return replacementOption
}
// RemainingBuffer returns the remaining (unused) part of the new option buffer,
// into which a new option may be written.
func (i *IPv4OptionIterator) RemainingBuffer() IPv4Options {
@@ -649,6 +659,17 @@ func (i *IPv4OptionIterator) Next() (IPv4Option, bool, *IPv4OptParameterProblem)
}
retval := IPv4OptionRecordRoute(optionBody)
return &retval, false, nil
case IPv4OptionRouterAlertType:
if optLen != IPv4OptionRouterAlertLength {
i.ErrCursor++
return nil, false, &IPv4OptParameterProblem{
Pointer: i.ErrCursor,
NeedICMP: true,
}
}
retval := IPv4OptionRouterAlert(optionBody)
return &retval, false, nil
}
retval := IPv4OptionGeneric(optionBody)
return &retval, false, nil
@@ -896,11 +917,30 @@ const (
// payload of the router alert option.
IPv4OptionRouterAlertValue = 0
// iPv4OptionRouterAlertValueOffset is the offset for the value of a
// IPv4OptionRouterAlertValueOffset is the offset for the value of a
// RouterAlert option.
iPv4OptionRouterAlertValueOffset = 2
IPv4OptionRouterAlertValueOffset = 2
)
var _ IPv4Option = (*IPv4OptionRouterAlert)(nil)
// IPv4OptionRouterAlert is an IPv4 RouterAlert option defined by RFC 2113.
type IPv4OptionRouterAlert []byte
// Type implements IPv4Option.
func (*IPv4OptionRouterAlert) Type() IPv4OptionType { return IPv4OptionRouterAlertType }
// Size implements IPv4Option.
func (ra *IPv4OptionRouterAlert) Size() uint8 { return uint8(len(*ra)) }
// Contents implements IPv4Option.
func (ra *IPv4OptionRouterAlert) Contents() []byte { return []byte(*ra) }
// Value returns the value of the IPv4OptionRouterAlert.
func (ra *IPv4OptionRouterAlert) Value() uint16 {
return binary.BigEndian.Uint16(ra.Contents()[IPv4OptionRouterAlertValueOffset:])
}
// IPv4SerializableOption is an interface to represent serializable IPv4 option
// types.
type IPv4SerializableOption interface {
@@ -999,7 +1039,7 @@ func (*IPv4SerializableRouterAlertOption) optionType() IPv4OptionType {
// Length implements IPv4SerializableOption.
func (*IPv4SerializableRouterAlertOption) length() uint8 {
return IPv4OptionRouterAlertLength - iPv4OptionRouterAlertValueOffset
return IPv4OptionRouterAlertLength - IPv4OptionRouterAlertValueOffset
}
// SerializeInto implements IPv4SerializableOption.
+14 -7
View File
@@ -68,11 +68,17 @@ type MultiCounterIPStats struct {
// Output chain.
IPTablesOutputDropped tcpip.MultiCounterStat
// OptionTSReceived is the number of Timestamp options seen.
OptionTSReceived tcpip.MultiCounterStat
// TODO(https://gvisor.dev/issues/5529): Move the IPv4-only option stats out
// OptionRRReceived is the number of Record Route options seen.
OptionRRReceived tcpip.MultiCounterStat
// of IPStats.
// OptionTimestampReceived is the number of Timestamp options seen.
OptionTimestampReceived tcpip.MultiCounterStat
// OptionRecordRouteReceived is the number of Record Route options seen.
OptionRecordRouteReceived tcpip.MultiCounterStat
// OptionRouterAlertReceived is the number of Router Alert options seen.
OptionRouterAlertReceived tcpip.MultiCounterStat
// OptionUnknownReceived is the number of unknown IP options seen.
OptionUnknownReceived tcpip.MultiCounterStat
@@ -92,9 +98,10 @@ func (m *MultiCounterIPStats) Init(a, b *tcpip.IPStats) {
m.IPTablesPreroutingDropped.Init(a.IPTablesPreroutingDropped, b.IPTablesPreroutingDropped)
m.IPTablesInputDropped.Init(a.IPTablesInputDropped, b.IPTablesInputDropped)
m.IPTablesOutputDropped.Init(a.IPTablesOutputDropped, b.IPTablesOutputDropped)
m.OptionTSReceived.Init(a.OptionTSReceived, b.OptionTSReceived)
m.OptionRRReceived.Init(a.OptionRRReceived, b.OptionRRReceived)
m.OptionTimestampReceived.Init(a.OptionTimestampReceived, b.OptionTimestampReceived)
m.OptionRecordRouteReceived.Init(a.OptionRecordRouteReceived, b.OptionRecordRouteReceived)
m.OptionRouterAlertReceived.Init(a.OptionRouterAlertReceived, b.OptionRouterAlertReceived)
m.OptionUnknownReceived.Init(a.OptionUnknownReceived, b.OptionUnknownReceived)
}
// LINT.ThenChange(:MultiCounterIPStats, ../../tcpip.go:IPStats)
// LINT.ThenChange(:MultiCounterIPStats, ../../../tcpip.go:IPStats)
+1 -1
View File
@@ -214,7 +214,7 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer) {
op = &optionUsageReceive{}
}
var optProblem *header.IPv4OptParameterProblem
newOptions, optProblem = e.processIPOptions(pkt, opts, op)
newOptions, _, optProblem = e.processIPOptions(pkt, opts, op)
if optProblem != nil {
if optProblem.NeedICMP {
_ = e.protocol.returnError(&icmpReasonParamProblem{
+59 -4
View File
@@ -145,10 +145,57 @@ func (igmp *igmpState) init(ep *endpoint) {
})
}
// Precondition: igmp.ep.mu must be locked.
func (igmp *igmpState) isSourceIPValidLocked(src tcpip.Address, messageType header.IGMPType) bool {
if messageType == header.IGMPMembershipQuery {
// RFC 2236 does not require the IGMP implementation to check the source IP
// for Membership Query messages.
return true
}
// As per RFC 2236 section 10,
//
// Ignore the Report if you cannot identify the source address of the
// packet as belonging to a subnet assigned to the interface on which the
// packet was received.
//
// Ignore the Leave message if you cannot identify the source address of
// the packet as belonging to a subnet assigned to the interface on which
// the packet was received.
//
// Note: this rule applies to both V1 and V2 Membership Reports.
var isSourceIPValid bool
igmp.ep.mu.addressableEndpointState.ForEachPrimaryEndpoint(func(addressEndpoint stack.AddressEndpoint) bool {
if subnet := addressEndpoint.Subnet(); subnet.Contains(src) {
isSourceIPValid = true
return false
}
return true
})
return isSourceIPValid
}
// Precondition: igmp.ep.mu must be locked.
func (igmp *igmpState) isPacketValidLocked(pkt *stack.PacketBuffer, messageType header.IGMPType, hasRouterAlertOption bool) bool {
// We can safely assume that the IP header is valid if we got this far.
iph := header.IPv4(pkt.NetworkHeader().View())
// As per RFC 2236 section 2,
//
// All IGMP messages described in this document are sent with IP TTL 1, and
// contain the IP Router Alert option [RFC 2113] in their IP header.
if !hasRouterAlertOption || iph.TTL() != header.IGMPTTL {
return false
}
return igmp.isSourceIPValidLocked(iph.SourceAddress(), messageType)
}
// handleIGMP handles an IGMP packet.
//
// Precondition: igmp.ep.mu must be locked.
func (igmp *igmpState) handleIGMP(pkt *stack.PacketBuffer) {
func (igmp *igmpState) handleIGMP(pkt *stack.PacketBuffer, hasRouterAlertOption bool) {
received := igmp.ep.stats.igmp.packetsReceived
headerView, ok := pkt.Data.PullUp(header.IGMPMinimumSize)
if !ok {
@@ -168,30 +215,38 @@ func (igmp *igmpState) handleIGMP(pkt *stack.PacketBuffer) {
return
}
isValid := func(minimumSize int) bool {
return len(headerView) >= minimumSize && igmp.isPacketValidLocked(pkt, h.Type(), hasRouterAlertOption)
}
switch h.Type() {
case header.IGMPMembershipQuery:
received.membershipQuery.Increment()
if len(headerView) < header.IGMPQueryMinimumSize {
if !isValid(header.IGMPQueryMinimumSize) {
received.invalid.Increment()
return
}
igmp.handleMembershipQuery(h.GroupAddress(), h.MaxRespTime())
case header.IGMPv1MembershipReport:
received.v1MembershipReport.Increment()
if len(headerView) < header.IGMPReportMinimumSize {
if !isValid(header.IGMPReportMinimumSize) {
received.invalid.Increment()
return
}
igmp.handleMembershipReport(h.GroupAddress())
case header.IGMPv2MembershipReport:
received.v2MembershipReport.Increment()
if len(headerView) < header.IGMPReportMinimumSize {
if !isValid(header.IGMPReportMinimumSize) {
received.invalid.Increment()
return
}
igmp.handleMembershipReport(h.GroupAddress())
case header.IGMPLeaveGroup:
received.leaveGroup.Increment()
if !isValid(header.IGMPLeaveMessageMinimumSize) {
received.invalid.Increment()
return
}
// As per RFC 2236 Section 6, Page 7: "IGMP messages other than Query or
// Report, are ignored in all states"
+170 -23
View File
@@ -29,22 +29,25 @@ import (
)
const (
linkAddr = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06")
addr = tcpip.Address("\x0a\x00\x00\x01")
multicastAddr = tcpip.Address("\xe0\x00\x00\x03")
nicID = 1
linkAddr = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06")
stackAddr = tcpip.Address("\x0a\x00\x00\x01")
remoteAddr = tcpip.Address("\x0a\x00\x00\x02")
multicastAddr = tcpip.Address("\xe0\x00\x00\x03")
nicID = 1
defaultTTL = 1
defaultPrefixLength = 24
)
// 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.
func validateIgmpPacket(t *testing.T, p channel.PacketInfo, remoteAddress tcpip.Address, igmpType header.IGMPType, maxRespTime byte, groupAddress tcpip.Address) {
func validateIgmpPacket(t *testing.T, p channel.PacketInfo, igmpType header.IGMPType, maxRespTime byte, srcAddr, dstAddr, groupAddress tcpip.Address) {
t.Helper()
payload := header.IPv4(stack.PayloadSince(p.Pkt.NetworkHeader()))
checker.IPv4(t, payload,
checker.SrcAddr(addr),
checker.DstAddr(remoteAddress),
checker.SrcAddr(srcAddr),
checker.DstAddr(dstAddr),
// TTL for an IGMP message must be 1 as per RFC 2236 section 2.
checker.TTL(1),
checker.IPv4RouterAlert(),
@@ -77,20 +80,27 @@ func createStack(t *testing.T, igmpEnabled bool) (*channel.Endpoint, *stack.Stac
return e, s, clock
}
func createAndInjectIGMPPacket(e *channel.Endpoint, igmpType header.IGMPType, maxRespTime byte, groupAddress tcpip.Address) {
buf := buffer.NewView(header.IPv4MinimumSize + header.IGMPQueryMinimumSize)
func createAndInjectIGMPPacket(e *channel.Endpoint, igmpType header.IGMPType, maxRespTime byte, ttl uint8, srcAddr, dstAddr, groupAddress tcpip.Address, hasRouterAlertOption bool) {
var options header.IPv4OptionsSerializer
if hasRouterAlertOption {
options = header.IPv4OptionsSerializer{
&header.IPv4SerializableRouterAlertOption{},
}
}
buf := buffer.NewView(header.IPv4MinimumSize + int(options.Length()) + header.IGMPQueryMinimumSize)
ip := header.IPv4(buf)
ip.Encode(&header.IPv4Fields{
TotalLength: uint16(len(buf)),
TTL: 1,
TTL: ttl,
Protocol: uint8(header.IGMPProtocolNumber),
SrcAddr: header.IPv4Any,
DstAddr: header.IPv4AllSystems,
SrcAddr: srcAddr,
DstAddr: dstAddr,
Options: options,
})
ip.SetChecksum(^ip.CalculateChecksum())
igmp := header.IGMP(buf[header.IPv4MinimumSize:])
igmp := header.IGMP(ip.Payload())
igmp.SetType(igmpType)
igmp.SetMaxRespTime(maxRespTime)
igmp.SetGroupAddress(groupAddress)
@@ -106,8 +116,9 @@ func createAndInjectIGMPPacket(e *channel.Endpoint, igmpType header.IGMPType, ma
// cycles.
func TestIGMPV1Present(t *testing.T) {
e, s, clock := createStack(t, true)
if err := s.AddAddress(nicID, ipv4.ProtocolNumber, addr); err != nil {
t.Fatalf("AddAddress(%d, %d, %s): %s", nicID, ipv4.ProtocolNumber, addr, err)
addr := tcpip.AddressWithPrefix{Address: stackAddr, PrefixLen: defaultPrefixLength}
if err := s.AddAddressWithPrefix(nicID, ipv4.ProtocolNumber, addr); err != nil {
t.Fatalf("AddAddressWithPrefix(%d, %d, %s): %s", nicID, ipv4.ProtocolNumber, addr, err)
}
if err := s.JoinGroup(ipv4.ProtocolNumber, nicID, multicastAddr); err != nil {
@@ -124,7 +135,7 @@ func TestIGMPV1Present(t *testing.T) {
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)
validateIgmpPacket(t, p, header.IGMPv2MembershipReport, 0, stackAddr, multicastAddr, multicastAddr)
}
if t.Failed() {
t.FailNow()
@@ -134,7 +145,7 @@ func TestIGMPV1Present(t *testing.T) {
// membership query except the Max Response Time is set to 0, which will tell
// the stack that this is a router using IGMPv1. Send it to the all systems
// group which is the only group this host belongs to.
createAndInjectIGMPPacket(e, header.IGMPMembershipQuery, 0, header.IPv4AllSystems)
createAndInjectIGMPPacket(e, header.IGMPMembershipQuery, 0, defaultTTL, remoteAddr, stackAddr, header.IPv4AllSystems, true /* hasRouterAlertOption */)
if got := s.Stats().IGMP.PacketsReceived.MembershipQuery.Value(); got != 1 {
t.Fatalf("got Membership Queries received = %d, want = 1", got)
}
@@ -159,7 +170,7 @@ func TestIGMPV1Present(t *testing.T) {
if got := s.Stats().IGMP.PacketsSent.V1MembershipReport.Value(); got != 1 {
t.Fatalf("got V1MembershipReport messages sent = %d, want = 1", got)
}
validateIgmpPacket(t, p, multicastAddr, header.IGMPv1MembershipReport, 0, multicastAddr)
validateIgmpPacket(t, p, header.IGMPv1MembershipReport, 0, stackAddr, multicastAddr, multicastAddr)
}
// Cycling the interface should reset the V1 present flag.
@@ -177,7 +188,7 @@ func TestIGMPV1Present(t *testing.T) {
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)
validateIgmpPacket(t, p, header.IGMPv2MembershipReport, 0, stackAddr, multicastAddr, multicastAddr)
}
}
@@ -200,8 +211,8 @@ func TestSendQueuedIGMPReports(t *testing.T) {
// The initial set of IGMP reports that were queued should be sent once an
// address is assigned.
if err := s.AddAddress(nicID, ipv4.ProtocolNumber, addr); err != nil {
t.Fatalf("AddAddress(%d, %d, %s): %s", nicID, ipv4.ProtocolNumber, addr, err)
if err := s.AddAddress(nicID, ipv4.ProtocolNumber, stackAddr); err != nil {
t.Fatalf("AddAddress(%d, %d, %s): %s", nicID, ipv4.ProtocolNumber, stackAddr, err)
}
if got := reportStat.Value(); got != 1 {
t.Errorf("got reportStat.Value() = %d, want = 1", got)
@@ -209,7 +220,7 @@ func TestSendQueuedIGMPReports(t *testing.T) {
if p, ok := e.Read(); !ok {
t.Error("expected to send an IGMP membership report")
} else {
validateIgmpPacket(t, p, multicastAddr, header.IGMPv2MembershipReport, 0, multicastAddr)
validateIgmpPacket(t, p, header.IGMPv2MembershipReport, 0, stackAddr, multicastAddr, multicastAddr)
}
if t.Failed() {
t.FailNow()
@@ -221,7 +232,7 @@ func TestSendQueuedIGMPReports(t *testing.T) {
if p, ok := e.Read(); !ok {
t.Error("expected to send an IGMP membership report")
} else {
validateIgmpPacket(t, p, multicastAddr, header.IGMPv2MembershipReport, 0, multicastAddr)
validateIgmpPacket(t, p, header.IGMPv2MembershipReport, 0, stackAddr, multicastAddr, multicastAddr)
}
if t.Failed() {
t.FailNow()
@@ -234,3 +245,139 @@ func TestSendQueuedIGMPReports(t *testing.T) {
t.Fatalf("got unexpected packet = %#v", p)
}
}
func TestIGMPPacketValidation(t *testing.T) {
tests := []struct {
name string
messageType header.IGMPType
stackAddresses []tcpip.AddressWithPrefix
srcAddr tcpip.Address
includeRouterAlertOption bool
ttl uint8
expectValidIGMP bool
getMessageTypeStatValue func(tcpip.Stats) uint64
}{
{
name: "valid",
messageType: header.IGMPLeaveGroup,
includeRouterAlertOption: true,
stackAddresses: []tcpip.AddressWithPrefix{{Address: stackAddr, PrefixLen: 24}},
srcAddr: remoteAddr,
ttl: 1,
expectValidIGMP: true,
getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.IGMP.PacketsReceived.LeaveGroup.Value() },
},
{
name: "bad ttl",
messageType: header.IGMPv1MembershipReport,
includeRouterAlertOption: true,
stackAddresses: []tcpip.AddressWithPrefix{{Address: stackAddr, PrefixLen: 24}},
srcAddr: remoteAddr,
ttl: 2,
expectValidIGMP: false,
getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.IGMP.PacketsReceived.V1MembershipReport.Value() },
},
{
name: "missing router alert ip option",
messageType: header.IGMPv2MembershipReport,
includeRouterAlertOption: false,
stackAddresses: []tcpip.AddressWithPrefix{{Address: stackAddr, PrefixLen: 24}},
srcAddr: remoteAddr,
ttl: 1,
expectValidIGMP: false,
getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.IGMP.PacketsReceived.V2MembershipReport.Value() },
},
{
name: "igmp leave group and src ip does not belong to nic subnet",
messageType: header.IGMPLeaveGroup,
includeRouterAlertOption: true,
stackAddresses: []tcpip.AddressWithPrefix{{Address: stackAddr, PrefixLen: 24}},
srcAddr: tcpip.Address("\x0a\x00\x01\x02"),
ttl: 1,
expectValidIGMP: false,
getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.IGMP.PacketsReceived.LeaveGroup.Value() },
},
{
name: "igmp query and src ip does not belong to nic subnet",
messageType: header.IGMPMembershipQuery,
includeRouterAlertOption: true,
stackAddresses: []tcpip.AddressWithPrefix{{Address: stackAddr, PrefixLen: 24}},
srcAddr: tcpip.Address("\x0a\x00\x01\x02"),
ttl: 1,
expectValidIGMP: true,
getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.IGMP.PacketsReceived.MembershipQuery.Value() },
},
{
name: "igmp report v1 and src ip does not belong to nic subnet",
messageType: header.IGMPv1MembershipReport,
includeRouterAlertOption: true,
stackAddresses: []tcpip.AddressWithPrefix{{Address: stackAddr, PrefixLen: 24}},
srcAddr: tcpip.Address("\x0a\x00\x01\x02"),
ttl: 1,
expectValidIGMP: false,
getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.IGMP.PacketsReceived.V1MembershipReport.Value() },
},
{
name: "igmp report v2 and src ip does not belong to nic subnet",
messageType: header.IGMPv2MembershipReport,
includeRouterAlertOption: true,
stackAddresses: []tcpip.AddressWithPrefix{{Address: stackAddr, PrefixLen: 24}},
srcAddr: tcpip.Address("\x0a\x00\x01\x02"),
ttl: 1,
expectValidIGMP: false,
getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.IGMP.PacketsReceived.V2MembershipReport.Value() },
},
{
name: "src ip belongs to the subnet of the nic's second address",
messageType: header.IGMPv2MembershipReport,
includeRouterAlertOption: true,
stackAddresses: []tcpip.AddressWithPrefix{
{Address: tcpip.Address("\x0a\x00\x0f\x01"), PrefixLen: 24},
{Address: stackAddr, PrefixLen: 24},
},
srcAddr: remoteAddr,
ttl: 1,
expectValidIGMP: true,
getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.IGMP.PacketsReceived.V2MembershipReport.Value() },
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
e, s, _ := createStack(t, true)
for _, address := range test.stackAddresses {
if err := s.AddAddressWithPrefix(nicID, ipv4.ProtocolNumber, address); err != nil {
t.Fatalf("AddAddressWithPrefix(%d, %d, %s): %s", nicID, ipv4.ProtocolNumber, address, err)
}
}
stats := s.Stats()
// Verify that every relevant stats is zero'd before we send a packet.
if got := test.getMessageTypeStatValue(s.Stats()); got != 0 {
t.Errorf("got test.getMessageTypeStatValue(s.Stats()) = %d, want = 0", got)
}
if got := stats.IGMP.PacketsReceived.Invalid.Value(); got != 0 {
t.Errorf("got stats.IGMP.PacketsReceived.Invalid.Value() = %d, want = 0", got)
}
if got := stats.IP.PacketsDelivered.Value(); got != 0 {
t.Fatalf("got stats.IP.PacketsDelivered.Value() = %d, want = 0", got)
}
createAndInjectIGMPPacket(e, test.messageType, 0, test.ttl, test.srcAddr, header.IPv4AllSystems, header.IPv4AllSystems, test.includeRouterAlertOption)
// We always expect the packet to pass IP validation.
if got := stats.IP.PacketsDelivered.Value(); got != 1 {
t.Fatalf("got stats.IP.PacketsDelivered.Value() = %d, want = 1", got)
}
// Even when the IGMP-specific validation checks fail, we expect the
// corresponding IGMP counter to be incremented.
if got := test.getMessageTypeStatValue(s.Stats()); got != 1 {
t.Errorf("got test.getMessageTypeStatValue(s.Stats()) = %d, want = 1", got)
}
var expectedInvalidCount uint64
if !test.expectValidIGMP {
expectedInvalidCount = 1
}
if got := stats.IGMP.PacketsReceived.Invalid.Value(); got != expectedInvalidCount {
t.Errorf("got stats.IGMP.PacketsReceived.Invalid.Value() = %d, want = %d", got, expectedInvalidCount)
}
})
}
}
+87 -51
View File
@@ -562,7 +562,7 @@ func (e *endpoint) forwardPacket(pkt *stack.PacketBuffer) tcpip.Error {
}
if opts := h.Options(); len(opts) != 0 {
newOpts, optProblem := e.processIPOptions(pkt, opts, &optionUsageForward{})
newOpts, _, optProblem := e.processIPOptions(pkt, opts, &optionUsageForward{})
if optProblem != nil {
if optProblem.NeedICMP {
_ = e.protocol.returnError(&icmpReasonParamProblem{
@@ -776,7 +776,7 @@ func (e *endpoint) handlePacket(pkt *stack.PacketBuffer) {
// If there are options we need to check them before we do assembly
// or we could be assembling errant packets. However we do not change the
// options as that could lead to double processing later.
if _, optProblem := e.processIPOptions(pkt, opts, &optionUsageVerify{}); optProblem != nil {
if _, _, optProblem := e.processIPOptions(pkt, opts, &optionUsageVerify{}); optProblem != nil {
if optProblem.NeedICMP {
_ = e.protocol.returnError(&icmpReasonParamProblem{
pointer: optProblem.Pointer,
@@ -846,8 +846,9 @@ func (e *endpoint) handlePacket(pkt *stack.PacketBuffer) {
return
}
// ICMP handles options itself but do it here for all remaining destinations.
var hasRouterAlertOption bool
if opts := h.Options(); len(opts) != 0 {
newOpts, optProblem := e.processIPOptions(pkt, opts, &optionUsageReceive{})
newOpts, processedOpts, optProblem := e.processIPOptions(pkt, opts, &optionUsageReceive{})
if optProblem != nil {
if optProblem.NeedICMP {
_ = e.protocol.returnError(&icmpReasonParamProblem{
@@ -858,6 +859,7 @@ func (e *endpoint) handlePacket(pkt *stack.PacketBuffer) {
}
return
}
hasRouterAlertOption = processedOpts.routerAlert
copied := copy(opts, newOpts)
if copied != len(newOpts) {
panic(fmt.Sprintf("copied %d bytes of new options, expected %d bytes", copied, len(newOpts)))
@@ -869,7 +871,7 @@ func (e *endpoint) handlePacket(pkt *stack.PacketBuffer) {
}
if p == header.IGMPProtocolNumber {
e.mu.Lock()
e.mu.igmp.handleIGMP(pkt)
e.mu.igmp.handleIGMP(pkt, hasRouterAlertOption)
e.mu.Unlock()
return
}
@@ -1291,9 +1293,12 @@ type optionActions struct {
// timestamp controls what to do with a Timestamp option.
timestamp optionAction
// recordroute controls what to do with a Record Route option.
// recordRoute controls what to do with a Record Route option.
recordRoute optionAction
// routerAlert controls what to do with a Router Alert option.
routerAlert optionAction
// unknown controls what to do with an unknown option.
unknown optionAction
}
@@ -1314,6 +1319,7 @@ func (*optionUsageVerify) actions() optionActions {
return optionActions{
timestamp: optionVerify,
recordRoute: optionVerify,
routerAlert: optionVerify,
unknown: optionRemove,
}
}
@@ -1327,6 +1333,7 @@ func (*optionUsageReceive) actions() optionActions {
return optionActions{
timestamp: optionProcess,
recordRoute: optionProcess,
routerAlert: optionVerify,
unknown: optionPass,
}
}
@@ -1341,6 +1348,7 @@ func (*optionUsageForward) actions() optionActions {
return optionActions{
timestamp: optionProcess,
recordRoute: optionProcess,
routerAlert: optionVerify,
unknown: optionPass,
}
}
@@ -1354,6 +1362,7 @@ func (*optionUsageEcho) actions() optionActions {
return optionActions{
timestamp: optionProcess,
recordRoute: optionProcess,
routerAlert: optionVerify,
unknown: optionRemove,
}
}
@@ -1551,45 +1560,64 @@ func handleRecordRoute(rrOpt header.IPv4OptionRecordRoute, localAddress tcpip.Ad
return nil
}
// handleRouterAlert performs sanity checks on a Router Alert option.
func handleRouterAlert(raOpt header.IPv4OptionRouterAlert) *header.IPv4OptParameterProblem {
// Only the zero value is acceptable, as per RFC 2113, section 2.1:
// Value: A two octet code with the following values:
// 0 - Router shall examine packet
// 1-65535 - Reserved
if raOpt.Value() != header.IPv4OptionRouterAlertValue {
return &header.IPv4OptParameterProblem{
Pointer: header.IPv4OptionRouterAlertValueOffset,
NeedICMP: true,
}
}
return nil
}
type optionTracker struct {
timestamp bool
recordRoute bool
routerAlert bool
}
// processIPOptions parses the IPv4 options and produces a new set of options
// suitable for use in the next step of packet processing as informed by usage.
// The original will not be touched.
//
// Returns
// - The location of an error if there was one (or 0 if no error)
// - If there is an error, information as to what it was was.
// - The replacement option set.
func (e *endpoint) processIPOptions(pkt *stack.PacketBuffer, orig header.IPv4Options, usage optionsUsage) (header.IPv4Options, *header.IPv4OptParameterProblem) {
// If there were no errors during parsing, the new set of options is returned as
// a new buffer.
func (e *endpoint) processIPOptions(pkt *stack.PacketBuffer, orig header.IPv4Options, usage optionsUsage) (header.IPv4Options, optionTracker, *header.IPv4OptParameterProblem) {
stats := e.stats.ip
opts := header.IPv4Options(orig)
optIter := opts.MakeIterator()
// Each option other than NOP must only appear (RFC 791 section 3.1, at the
// definition of every type). Keep track of each of the possible types in
// the 8 bit 'type' field.
// Except NOP, each option must only appear at most once (RFC 791 section 3.1,
// at the definition of every type).
// Keep track of each option we find to enable duplicate option detection.
var seenOptions [math.MaxUint8 + 1]bool
// TODO(gvisor.dev/issue/4586):
// This will need tweaking when we start really forwarding packets
// as we may need to get two addresses, for rx and tx interfaces.
// We will also have to take usage into account.
// TODO(https://gvisor.dev/issue/4586): This will need tweaking when we start
// really forwarding packets as we may need to get two addresses, for rx and
// tx interfaces. We will also have to take usage into account.
prefixedAddress, ok := e.protocol.stack.GetMainNICAddress(e.nic.ID(), ProtocolNumber)
localAddress := prefixedAddress.Address
if !ok {
h := header.IPv4(pkt.NetworkHeader().View())
dstAddr := h.DestinationAddress()
if pkt.NetworkPacketInfo.LocalAddressBroadcast || header.IsV4MulticastAddress(dstAddr) {
return nil, &header.IPv4OptParameterProblem{
return nil, optionTracker{}, &header.IPv4OptParameterProblem{
NeedICMP: false,
}
}
localAddress = dstAddr
}
var optionsProcessed optionTracker
for {
option, done, optProblem := optIter.Next()
if done || optProblem != nil {
return optIter.Finalize(), optProblem
return optIter.Finalize(), optionsProcessed, optProblem
}
optType := option.Type()
if optType == header.IPv4OptionNOPType {
@@ -1598,53 +1626,61 @@ func (e *endpoint) processIPOptions(pkt *stack.PacketBuffer, orig header.IPv4Opt
}
if optType == header.IPv4OptionListEndType {
optIter.PushNOPOrEnd(optType)
return optIter.Finalize(), nil
return optIter.Finalize(), optionsProcessed, nil
}
// check for repeating options (multiple NOPs are OK)
if seenOptions[optType] {
return nil, &header.IPv4OptParameterProblem{
return nil, optionTracker{}, &header.IPv4OptParameterProblem{
Pointer: optIter.ErrCursor,
NeedICMP: true,
}
}
seenOptions[optType] = true
optLen := int(option.Size())
switch option := option.(type) {
case *header.IPv4OptionTimestamp:
stats.OptionTSReceived.Increment()
if usage.actions().timestamp != optionRemove {
clock := e.protocol.stack.Clock()
newBuffer := optIter.RemainingBuffer()[:len(*option)]
_ = copy(newBuffer, option.Contents())
if optProblem := handleTimestamp(header.IPv4OptionTimestamp(newBuffer), localAddress, clock, usage); optProblem != nil {
optProblem.Pointer += optIter.ErrCursor
return nil, optProblem
optLen, optProblem := func() (int, *header.IPv4OptParameterProblem) {
switch option := option.(type) {
case *header.IPv4OptionTimestamp:
stats.OptionTimestampReceived.Increment()
optionsProcessed.timestamp = true
if usage.actions().timestamp != optionRemove {
clock := e.protocol.stack.Clock()
newBuffer := optIter.InitReplacement(option)
optProblem := handleTimestamp(header.IPv4OptionTimestamp(newBuffer), localAddress, clock, usage)
return len(newBuffer), optProblem
}
optIter.ConsumeBuffer(optLen)
}
case *header.IPv4OptionRecordRoute:
stats.OptionRRReceived.Increment()
if usage.actions().recordRoute != optionRemove {
newBuffer := optIter.RemainingBuffer()[:len(*option)]
_ = copy(newBuffer, option.Contents())
if optProblem := handleRecordRoute(header.IPv4OptionRecordRoute(newBuffer), localAddress, usage); optProblem != nil {
optProblem.Pointer += optIter.ErrCursor
return nil, optProblem
case *header.IPv4OptionRecordRoute:
stats.OptionRecordRouteReceived.Increment()
optionsProcessed.recordRoute = true
if usage.actions().recordRoute != optionRemove {
newBuffer := optIter.InitReplacement(option)
optProblem := handleRecordRoute(header.IPv4OptionRecordRoute(newBuffer), localAddress, usage)
return len(newBuffer), optProblem
}
optIter.ConsumeBuffer(optLen)
}
default:
stats.OptionUnknownReceived.Increment()
if usage.actions().unknown == optionPass {
newBuffer := optIter.RemainingBuffer()[:optLen]
// Arguments already heavily checked.. ignore result.
_ = copy(newBuffer, option.Contents())
optIter.ConsumeBuffer(optLen)
case *header.IPv4OptionRouterAlert:
stats.OptionRouterAlertReceived.Increment()
optionsProcessed.routerAlert = true
if usage.actions().routerAlert != optionRemove {
newBuffer := optIter.InitReplacement(option)
optProblem := handleRouterAlert(header.IPv4OptionRouterAlert(newBuffer))
return len(newBuffer), optProblem
}
default:
stats.OptionUnknownReceived.Increment()
if usage.actions().unknown == optionPass {
return len(optIter.InitReplacement(option)), nil
}
}
return 0, nil
}()
if optProblem != nil {
optProblem.Pointer += optIter.ErrCursor
return nil, optionTracker{}, optProblem
}
optIter.ConsumeBuffer(optLen)
}
}
+17 -13
View File
@@ -35,8 +35,9 @@ import (
const (
linkAddr = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06")
ipv4Addr = tcpip.Address("\x0a\x00\x00\x01")
ipv6Addr = tcpip.Address("\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
stackIPv4Addr = tcpip.Address("\x0a\x00\x00\x01")
defaultIPv4PrefixLength = 24
ipv6Addr = tcpip.Address("\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
ipv4MulticastAddr1 = tcpip.Address("\xe0\x00\x00\x03")
ipv4MulticastAddr2 = tcpip.Address("\xe0\x00\x00\x04")
@@ -99,7 +100,7 @@ func validateIGMPPacket(t *testing.T, p channel.PacketInfo, remoteAddress tcpip.
payload := header.IPv4(stack.PayloadSince(p.Pkt.NetworkHeader()))
checker.IPv4(t, payload,
checker.SrcAddr(ipv4Addr),
checker.SrcAddr(stackIPv4Addr),
checker.DstAddr(remoteAddress),
// TTL for an IGMP message must be 1 as per RFC 2236 section 2.
checker.TTL(1),
@@ -145,8 +146,12 @@ func createStackWithLinkEndpoint(t *testing.T, v4, mgpEnabled bool, e stack.Link
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
}
if err := s.AddAddress(nicID, ipv4.ProtocolNumber, ipv4Addr); err != nil {
t.Fatalf("AddAddress(%d, %d, %s): %s", nicID, ipv4.ProtocolNumber, ipv4Addr, err)
addr := tcpip.AddressWithPrefix{
Address: stackIPv4Addr,
PrefixLen: defaultIPv4PrefixLength,
}
if err := s.AddAddressWithPrefix(nicID, ipv4.ProtocolNumber, addr); err != nil {
t.Fatalf("AddAddressWithPrefix(%d, %d, %s): %s", nicID, ipv4.ProtocolNumber, addr, err)
}
if err := s.AddAddress(nicID, ipv6.ProtocolNumber, ipv6Addr); err != nil {
t.Fatalf("AddAddress(%d, %d, %s): %s", nicID, ipv6.ProtocolNumber, ipv6Addr, err)
@@ -202,24 +207,23 @@ func checkInitialIPv6Groups(t *testing.T, e *channel.Endpoint, s *stack.Stack, c
// createAndInjectIGMPPacket creates and injects an IGMP packet with the
// specified fields.
//
// Note, the router alert option is not included in this packet.
//
// TODO(b/162198658): set the router alert option.
func createAndInjectIGMPPacket(e *channel.Endpoint, igmpType byte, maxRespTime byte, groupAddress tcpip.Address) {
buf := buffer.NewView(header.IPv4MinimumSize + header.IGMPQueryMinimumSize)
options := header.IPv4OptionsSerializer{
&header.IPv4SerializableRouterAlertOption{},
}
buf := buffer.NewView(header.IPv4MinimumSize + int(options.Length()) + header.IGMPQueryMinimumSize)
ip := header.IPv4(buf)
ip.Encode(&header.IPv4Fields{
TotalLength: uint16(len(buf)),
TTL: header.IGMPTTL,
Protocol: uint8(header.IGMPProtocolNumber),
SrcAddr: header.IPv4Any,
SrcAddr: remoteIPv4Addr,
DstAddr: header.IPv4AllSystems,
Options: options,
})
ip.SetChecksum(^ip.CalculateChecksum())
igmp := header.IGMP(buf[header.IPv4MinimumSize:])
igmp := header.IGMP(ip.Payload())
igmp.SetType(header.IGMPType(igmpType))
igmp.SetMaxRespTime(maxRespTime)
igmp.SetGroupAddress(groupAddress)
+9 -4
View File
@@ -1581,11 +1581,16 @@ type IPStats struct {
// the Output chain.
IPTablesOutputDropped *StatCounter
// OptionTSReceived is the number of Timestamp options seen.
OptionTSReceived *StatCounter
// TODO(https://gvisor.dev/issues/5529): Move the IPv4-only option stats out
// of IPStats.
// OptionTimestampReceived is the number of Timestamp options seen.
OptionTimestampReceived *StatCounter
// OptionRRReceived is the number of Record Route options seen.
OptionRRReceived *StatCounter
// OptionRecordRouteReceived is the number of Record Route options seen.
OptionRecordRouteReceived *StatCounter
// OptionRouterAlertReceived is the number of Router Alert options seen.
OptionRouterAlertReceived *StatCounter
// OptionUnknownReceived is the number of unknown IP options seen.
OptionUnknownReceived *StatCounter