Accept no buffer space errors when forwarding

The outgoing device queue may be full when forwarding packets and those
errors should not result in a panic.

PiperOrigin-RevId: 499946528
This commit is contained in:
Ghanan Gowripalan
2023-01-05 11:16:56 -08:00
committed by gVisor bot
parent f67a389f0a
commit 8ebc4d767a
6 changed files with 132 additions and 56 deletions
+8
View File
@@ -34,6 +34,14 @@ func (*ErrTTLExceeded) isForwardingError() {}
func (*ErrTTLExceeded) String() string { return "ttl exceeded" }
// ErrOutgoingDeviceNoBufferSpace indicates that the outgoing device does not
// have enough space to hold a buffer.
type ErrOutgoingDeviceNoBufferSpace struct{}
func (*ErrOutgoingDeviceNoBufferSpace) isForwardingError() {}
func (*ErrOutgoingDeviceNoBufferSpace) String() string { return "no device buffer space" }
// ErrParameterProblem indicates the received packet had a problem with an IP
// parameter.
type ErrParameterProblem struct{}
+5
View File
@@ -64,6 +64,10 @@ type MultiCounterIPForwardingStats struct {
// were dropped due to insufficent buffer space in the pending packet queue.
NoMulticastPendingQueueBufferSpace tcpip.MultiCounterStat
// OutgoingDeviceNoBufferSpace is the number of packets that were dropped due
// to insufficient space in the outgoing device.
OutgoingDeviceNoBufferSpace tcpip.MultiCounterStat
// Errors is the number of IP packets received which could not be
// successfully forwarded.
Errors tcpip.MultiCounterStat
@@ -82,6 +86,7 @@ func (m *MultiCounterIPForwardingStats) Init(a, b *tcpip.IPForwardingStats) {
m.UnexpectedMulticastInputInterface.Init(a.UnexpectedMulticastInputInterface, b.UnexpectedMulticastInputInterface)
m.UnknownOutputEndpoint.Init(a.UnknownOutputEndpoint, b.UnknownOutputEndpoint)
m.NoMulticastPendingQueueBufferSpace.Init(a.NoMulticastPendingQueueBufferSpace, b.NoMulticastPendingQueueBufferSpace)
m.OutgoingDeviceNoBufferSpace.Init(a.OutgoingDeviceNoBufferSpace, b.OutgoingDeviceNoBufferSpace)
}
// LINT.ThenChange(:MultiCounterIPForwardingStats, ../../../tcpip.go:IPForwardingStats)
+5 -1
View File
@@ -691,6 +691,8 @@ func (e *endpoint) forwardPacketWithRoute(route *stack.Route, pkt stack.PacketBu
// necessary and the bit is also set.
_ = e.protocol.returnError(&icmpReasonFragmentationNeeded{}, pkt, false /* deliveredLocally */)
return &ip.ErrMessageTooLong{}
case *tcpip.ErrNoBufferSpace:
return &ip.ErrOutgoingDeviceNoBufferSpace{}
default:
return &ip.ErrOther{Err: err}
}
@@ -1105,7 +1107,7 @@ func (e *endpoint) handleValidatedPacket(h header.IPv4, pkt stack.PacketBufferPt
// counters.
func (e *endpoint) handleForwardingError(err ip.ForwardingError) {
stats := e.stats.ip
switch err.(type) {
switch err := err.(type) {
case nil:
return
case *ip.ErrLinkLocalSourceAddress:
@@ -1126,6 +1128,8 @@ func (e *endpoint) handleForwardingError(err ip.ForwardingError) {
stats.Forwarding.UnexpectedMulticastInputInterface.Increment()
case *ip.ErrUnknownOutputEndpoint:
stats.Forwarding.UnknownOutputEndpoint.Increment()
case *ip.ErrOutgoingDeviceNoBufferSpace:
stats.Forwarding.OutgoingDeviceNoBufferSpace.Increment()
default:
panic(fmt.Sprintf("unrecognized forwarding error: %s", err))
}
+5 -1
View File
@@ -1029,6 +1029,8 @@ func (e *endpoint) forwardPacketWithRoute(route *stack.Route, pkt stack.PacketBu
// outgoing link.
_ = e.protocol.returnError(&icmpReasonPacketTooBig{}, pkt, false /* deliveredLocally */)
return &ip.ErrMessageTooLong{}
case *tcpip.ErrNoBufferSpace:
return &ip.ErrOutgoingDeviceNoBufferSpace{}
default:
return &ip.ErrOther{Err: err}
}
@@ -1227,7 +1229,7 @@ func (e *endpoint) forwardMulticastPacketForOutgoingInterface(pkt stack.PacketBu
// counters.
func (e *endpoint) handleForwardingError(err ip.ForwardingError) {
stats := e.stats.ip
switch err.(type) {
switch err := err.(type) {
case nil:
return
case *ip.ErrLinkLocalSourceAddress:
@@ -1248,6 +1250,8 @@ func (e *endpoint) handleForwardingError(err ip.ForwardingError) {
stats.Forwarding.UnexpectedMulticastInputInterface.Increment()
case *ip.ErrUnknownOutputEndpoint:
stats.Forwarding.UnknownOutputEndpoint.Increment()
case *ip.ErrOutgoingDeviceNoBufferSpace:
stats.Forwarding.OutgoingDeviceNoBufferSpace.Increment()
default:
panic(fmt.Sprintf("unrecognized forwarding error: %s", err))
}
+4
View File
@@ -1739,6 +1739,10 @@ type IPForwardingStats struct {
// were dropped due to insufficent buffer space in the pending packet queue.
NoMulticastPendingQueueBufferSpace *StatCounter
// OutgoingDeviceNoBufferSpace is the number of packets that were dropped due
// to insufficient space in the outgoing device.
OutgoingDeviceNoBufferSpace *StatCounter
// Errors is the number of IP packets received which could not be
// successfully forwarded.
Errors *StatCounter
+105 -54
View File
@@ -349,6 +349,19 @@ func TestForwarding(t *testing.T) {
}
}
type fillableLinkEndpoint struct {
*channel.Endpoint
full bool
}
func (e *fillableLinkEndpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
if e.full {
return 0, &tcpip.ErrNoBufferSpace{}
}
return e.Endpoint.WritePackets(pkts)
}
func TestUnicastForwarding(t *testing.T) {
const (
nicID1 = 1
@@ -362,6 +375,7 @@ func TestUnicastForwarding(t *testing.T) {
tests := []struct {
name string
netProto tcpip.NetworkProtocolNumber
srcAddr, dstAddr tcpip.Address
rx func(*channel.Endpoint, tcpip.Address, tcpip.Address)
expectForward bool
@@ -369,6 +383,7 @@ func TestUnicastForwarding(t *testing.T) {
}{
{
name: "IPv4 link-local source",
netProto: ipv4.ProtocolNumber,
srcAddr: ipv4LinkLocalUnicastAddr,
dstAddr: utils.RemoteIPv4Addr,
rx: rxICMPv4EchoRequest,
@@ -376,6 +391,7 @@ func TestUnicastForwarding(t *testing.T) {
},
{
name: "IPv4 link-local destination",
netProto: ipv4.ProtocolNumber,
srcAddr: utils.RemoteIPv4Addr,
dstAddr: ipv4LinkLocalUnicastAddr,
rx: rxICMPv4EchoRequest,
@@ -383,6 +399,7 @@ func TestUnicastForwarding(t *testing.T) {
},
{
name: "IPv4 non-link-local unicast",
netProto: ipv4.ProtocolNumber,
srcAddr: utils.RemoteIPv4Addr,
dstAddr: utils.Ipv4Addr2.AddressWithPrefix.Address,
rx: rxICMPv4EchoRequest,
@@ -393,6 +410,7 @@ func TestUnicastForwarding(t *testing.T) {
},
{
name: "IPv6 link-local source",
netProto: ipv6.ProtocolNumber,
srcAddr: ipv6LinkLocalUnicastAddr,
dstAddr: utils.RemoteIPv6Addr,
rx: rxICMPv6EchoRequest,
@@ -400,6 +418,7 @@ func TestUnicastForwarding(t *testing.T) {
},
{
name: "IPv6 link-local destination",
netProto: ipv6.ProtocolNumber,
srcAddr: utils.RemoteIPv6Addr,
dstAddr: ipv6LinkLocalUnicastAddr,
rx: rxICMPv6EchoRequest,
@@ -407,6 +426,7 @@ func TestUnicastForwarding(t *testing.T) {
},
{
name: "IPv6 non-link-local unicast",
netProto: ipv6.ProtocolNumber,
srcAddr: utils.RemoteIPv6Addr,
dstAddr: utils.Ipv6Addr2.AddressWithPrefix.Address,
rx: rxICMPv6EchoRequest,
@@ -419,68 +439,99 @@ func TestUnicastForwarding(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
})
for _, full := range []bool{true, false} {
t.Run(fmt.Sprintf("Full=%t", full), func(t *testing.T) {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
})
e1 := channel.New(1, header.IPv6MinimumMTU, "")
defer e1.Close()
if err := s.CreateNIC(nicID1, e1); err != nil {
t.Fatalf("s.CreateNIC(%d, _): %s", nicID1, err)
}
e1 := channel.New(1, header.IPv6MinimumMTU, "")
defer e1.Close()
if err := s.CreateNIC(nicID1, e1); err != nil {
t.Fatalf("s.CreateNIC(%d, _): %s", nicID1, err)
}
e2 := channel.New(1, header.IPv6MinimumMTU, "")
defer e2.Close()
if err := s.CreateNIC(nicID2, e2); err != nil {
t.Fatalf("s.CreateNIC(%d, _): %s", nicID2, err)
}
e2 := fillableLinkEndpoint{Endpoint: channel.New(1, header.IPv6MinimumMTU, ""), full: full}
defer e2.Close()
if err := s.CreateNIC(nicID2, &e2); err != nil {
t.Fatalf("s.CreateNIC(%d, _): %s", nicID2, err)
}
protocolAddrV4 := tcpip.ProtocolAddress{
Protocol: ipv4.ProtocolNumber,
AddressWithPrefix: utils.Ipv4Addr,
}
if err := s.AddProtocolAddress(nicID2, protocolAddrV4, stack.AddressProperties{}); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID2, protocolAddrV4, err)
}
protocolAddrV6 := tcpip.ProtocolAddress{
Protocol: ipv6.ProtocolNumber,
AddressWithPrefix: utils.Ipv6Addr,
}
if err := s.AddProtocolAddress(nicID2, protocolAddrV6, stack.AddressProperties{}); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID2, protocolAddrV6, err)
}
protocolAddrV4 := tcpip.ProtocolAddress{
Protocol: ipv4.ProtocolNumber,
AddressWithPrefix: utils.Ipv4Addr,
}
if err := s.AddProtocolAddress(nicID2, protocolAddrV4, stack.AddressProperties{}); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID2, protocolAddrV4, err)
}
protocolAddrV6 := tcpip.ProtocolAddress{
Protocol: ipv6.ProtocolNumber,
AddressWithPrefix: utils.Ipv6Addr,
}
if err := s.AddProtocolAddress(nicID2, protocolAddrV6, stack.AddressProperties{}); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID2, protocolAddrV6, err)
}
if err := s.SetForwardingDefaultAndAllNICs(ipv4.ProtocolNumber, true); err != nil {
t.Fatalf("s.SetForwardingDefaultAndAllNICs(%d, true): %s", ipv4.ProtocolNumber, err)
}
if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, true); err != nil {
t.Fatalf("s.SetForwardingDefaultAndAllNICs(%d, true): %s", ipv6.ProtocolNumber, err)
}
if err := s.SetForwardingDefaultAndAllNICs(ipv4.ProtocolNumber, true); err != nil {
t.Fatalf("s.SetForwardingDefaultAndAllNICs(%d, true): %s", ipv4.ProtocolNumber, err)
}
if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, true); err != nil {
t.Fatalf("s.SetForwardingDefaultAndAllNICs(%d, true): %s", ipv6.ProtocolNumber, err)
}
s.SetRouteTable([]tcpip.Route{
{
Destination: header.IPv4EmptySubnet,
NIC: nicID2,
},
{
Destination: header.IPv6EmptySubnet,
NIC: nicID2,
},
})
s.SetRouteTable([]tcpip.Route{
{
Destination: header.IPv4EmptySubnet,
NIC: nicID2,
},
{
Destination: header.IPv6EmptySubnet,
NIC: nicID2,
},
})
test.rx(e1, test.srcAddr, test.dstAddr)
test.rx(e1, test.srcAddr, test.dstAddr)
p := e2.Read()
if (!p.IsNil()) != test.expectForward {
t.Fatalf("got e2.Read() = %#v, want = (_ == nil) = %t", p, test.expectForward)
}
expectForward := test.expectForward && !full
p := e2.Read()
if (!p.IsNil()) != expectForward {
t.Fatalf("got e2.Read() = %#v, want = (_ == nil) = %t", p, expectForward)
}
if test.expectForward {
payload := stack.PayloadSince(p.NetworkHeader())
defer payload.Release()
test.checker(t, payload)
p.DecRef()
if expectForward {
payload := stack.PayloadSince(p.NetworkHeader())
defer payload.Release()
test.checker(t, payload)
p.DecRef()
}
checkOutgoingDeviceNoBufferSpaceCounter := func(nicID tcpip.NICID, expectErr bool) {
t.Helper()
expectCounter := uint64(0)
if expectErr {
expectCounter = 1
}
netEP, err := s.GetNetworkEndpoint(nicID, test.netProto)
if err != nil {
t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, test.netProto, err)
}
stats := netEP.Stats()
ipStats, ok := stats.(stack.IPNetworkEndpointStats)
if !ok {
t.Fatalf("%#v is not a %T", stats, ipStats)
}
if got := ipStats.IPStats().Forwarding.OutgoingDeviceNoBufferSpace.Value(); got != expectCounter {
t.Errorf("got ipStats.IPStats().Forwarding.OutgoingDeviceNoBufferSpace.Value() = %d, want = %d", got, expectCounter)
}
}
checkOutgoingDeviceNoBufferSpaceCounter(nicID1, test.expectForward && full)
checkOutgoingDeviceNoBufferSpaceCounter(nicID2, false)
})
}
})
}