google / gvisor

Application Kernel for Containers

Home Page:https://gvisor.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

tcpip/stack: incorrect UDP UnknownPortErrors increment

xjasonlyu opened this issue · comments

Description

It started with a tool I wrote and I noticed the increasing UnknownPortErrors counts.

E.g., say we set a UDPTransportProtocolHandler like this:

udpForwarder := udp.NewForwarder(s, func(r *udp.ForwarderRequest) {
	var wq waiter.Queue
	ep, err := r.CreateEndpoint(&wq)
	if err != nil {
		return
	}
	// ...
})
s.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)

And when a UDP packet arrives to the stack, it comes to nic and is handled by (*nic).stack.demux.deliverPacket in (*nic).DeliverTransportPacket:

// DeliverTransportPacket delivers the packets to the appropriate transport
// protocol endpoint.
func (n *nic) DeliverTransportPacket(protocol tcpip.TransportProtocolNumber, pkt *PacketBuffer) TransportPacketDisposition {
state, ok := n.stack.transportProtocols[protocol]
if !ok {
n.stats.unknownL4ProtocolRcvdPacketCounts.Increment(uint64(protocol))
return TransportPacketProtocolUnreachable
}
transProto := state.proto
if pkt.TransportHeader().View().IsEmpty() {
n.stats.malformedL4RcvdPackets.Increment()
return TransportPacketHandled
}
srcPort, dstPort, err := transProto.ParsePorts(pkt.TransportHeader().View())
if err != nil {
n.stats.malformedL4RcvdPackets.Increment()
return TransportPacketHandled
}
netProto, ok := n.stack.networkProtocols[pkt.NetworkProtocolNumber]
if !ok {
panic(fmt.Sprintf("expected network protocol = %d, have = %#v", pkt.NetworkProtocolNumber, n.stack.networkProtocolNumbers()))
}
src, dst := netProto.ParseAddresses(pkt.NetworkHeader().View())
id := TransportEndpointID{
LocalPort: dstPort,
LocalAddress: dst,
RemotePort: srcPort,
RemoteAddress: src,
}
if n.stack.demux.deliverPacket(protocol, pkt, id) {
return TransportPacketHandled
}

Then, in the (*transportDemuxer).deliverPacket, it calls the eps.findEndpointLocked(id) but nil would be returned (because it's a new arrived packet, not registered yet).

// deliverPacket attempts to find one or more matching transport endpoints, and
// then, if matches are found, delivers the packet to them. Returns true if
// the packet no longer needs to be handled.
func (d *transportDemuxer) deliverPacket(protocol tcpip.TransportProtocolNumber, pkt *PacketBuffer, id TransportEndpointID) bool {
eps, ok := d.protocol[protocolIDs{pkt.NetworkProtocolNumber, protocol}]
if !ok {
return false
}
// If the packet is a UDP broadcast or multicast, then find all matching
// transport endpoints.
if protocol == header.UDPProtocolNumber && isInboundMulticastOrBroadcast(pkt, id.LocalAddress) {
eps.mu.RLock()
destEPs := eps.findAllEndpointsLocked(id)
eps.mu.RUnlock()
// Fail if we didn't find at least one matching transport endpoint.
if len(destEPs) == 0 {
d.stack.stats.UDP.UnknownPortErrors.Increment()
return false
}
// handlePacket takes may modify pkt, so each endpoint needs its own
// copy except for the final one.
for _, ep := range destEPs[:len(destEPs)-1] {
clone := pkt.Clone()
ep.handlePacket(id, clone)
clone.DecRef()
}
destEPs[len(destEPs)-1].handlePacket(id, pkt)
return true
}
// If the packet is a TCP packet with a unspecified source or non-unicast
// destination address, then do nothing further and instruct the caller to do
// the same. The network layer handles address validation for specified source
// addresses.
if protocol == header.TCPProtocolNumber && (!isSpecified(id.LocalAddress) || !isSpecified(id.RemoteAddress) || isInboundMulticastOrBroadcast(pkt, id.LocalAddress)) {
// TCP can only be used to communicate between a single source and a
// single destination; the addresses must be unicast.e
d.stack.stats.TCP.InvalidSegmentsReceived.Increment()
return true
}
eps.mu.RLock()
ep := eps.findEndpointLocked(id)
eps.mu.RUnlock()
if ep == nil {
if protocol == header.UDPProtocolNumber {
d.stack.stats.UDP.UnknownPortErrors.Increment()
}
return false
}
return ep.handlePacket(id, pkt)
}

So in the next if statement, since it's UDP packet, an UnknownPortErrors would be increased.

if protocol == header.UDPProtocolNumber {
	d.stack.stats.UDP.UnknownPortErrors.Increment()
}

However, there should not be an error here, since we have the defaultHandler to handle it later. Therefore, I think the right behavior for the stack is check if defaultHandler is nil and then decides the UDP.UnknownPortErrors.Increment().

Steps to reproduce

mentioned above.

runsc version

nil

docker version (if using docker)

nil

uname

nil

kubectl (if using Kubernetes)

nil

repo state (if built from source)

nil

runsc debug logs (if available)

nil

Is the increased UnknownPortErrors breaking anything? I feel that this is working as intended, and that in this particular use case it's not a useful value to you.

UnknownPortErrors tracks the stack being unable to find an endpoint for a packet. If netstack is used as described above, then it's simply counting a value that is irrelevant to this use case. For other use cases, the value is more useful.

No, it didn’t break anything. I just previously thought this shouldn’t be an error for this particular use case :)