github.com/noisysockets/netstack@v0.6.0/pkg/tcpip/stack/gro/gro.go (about)

     1  // Copyright 2022 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package gro implements generic receive offload.
    16  package gro
    17  
    18  import (
    19  	"bytes"
    20  	"fmt"
    21  
    22  	"github.com/noisysockets/netstack/pkg/tcpip"
    23  	"github.com/noisysockets/netstack/pkg/tcpip/header"
    24  	"github.com/noisysockets/netstack/pkg/tcpip/stack"
    25  )
    26  
    27  // TODO(b/256037250): Enable by default.
    28  // TODO(b/256037250): We parse headers here. We should save those headers in
    29  // PacketBuffers so they don't have to be re-parsed later.
    30  // TODO(b/256037250): I still see the occasional SACK block in the zero-loss
    31  // benchmark, which should not happen.
    32  // TODO(b/256037250): Some dispatchers, e.g. XDP and RecvMmsg, can receive
    33  // multiple packets at a time. Even if the GRO interval is 0, there is an
    34  // opportunity for coalescing.
    35  // TODO(b/256037250): We're doing some header parsing here, which presents the
    36  // opportunity to skip it later.
    37  // TODO(b/256037250): Can we pass a packet list up the stack too?
    38  
    39  const (
    40  	// groNBuckets is the number of GRO buckets.
    41  	groNBuckets = 8
    42  
    43  	groNBucketsMask = groNBuckets - 1
    44  
    45  	// groBucketSize is the size of each GRO bucket.
    46  	groBucketSize = 8
    47  
    48  	// groMaxPacketSize is the maximum size of a GRO'd packet.
    49  	groMaxPacketSize = 1 << 16 // 65KB.
    50  )
    51  
    52  // A groBucket holds packets that are undergoing GRO.
    53  type groBucket struct {
    54  	// count is the number of packets in the bucket.
    55  	count int
    56  
    57  	// packets is the linked list of packets.
    58  	packets groPacketList
    59  
    60  	// packetsPrealloc and allocIdxs are used to preallocate and reuse
    61  	// groPacket structs and avoid allocation.
    62  	packetsPrealloc [groBucketSize]groPacket
    63  
    64  	allocIdxs [groBucketSize]int
    65  }
    66  
    67  func (gb *groBucket) full() bool {
    68  	return gb.count == groBucketSize
    69  }
    70  
    71  // insert inserts pkt into the bucket.
    72  func (gb *groBucket) insert(pkt *stack.PacketBuffer, ipHdr []byte, tcpHdr header.TCP) {
    73  	groPkt := &gb.packetsPrealloc[gb.allocIdxs[gb.count]]
    74  	*groPkt = groPacket{
    75  		pkt:           pkt,
    76  		ipHdr:         ipHdr,
    77  		tcpHdr:        tcpHdr,
    78  		initialLength: pkt.Data().Size(), // pkt.Data() contains network header.
    79  		idx:           groPkt.idx,
    80  	}
    81  	gb.count++
    82  	gb.packets.PushBack(groPkt)
    83  }
    84  
    85  // removeOldest removes the oldest packet from gb and returns the contained
    86  // PacketBuffer. gb must not be empty.
    87  func (gb *groBucket) removeOldest() *stack.PacketBuffer {
    88  	pkt := gb.packets.Front()
    89  	gb.packets.Remove(pkt)
    90  	gb.count--
    91  	gb.allocIdxs[gb.count] = pkt.idx
    92  	ret := pkt.pkt
    93  	pkt.reset()
    94  	return ret
    95  }
    96  
    97  // removeOne removes a packet from gb. It also resets pkt to its zero value.
    98  func (gb *groBucket) removeOne(pkt *groPacket) {
    99  	gb.packets.Remove(pkt)
   100  	gb.count--
   101  	gb.allocIdxs[gb.count] = pkt.idx
   102  	pkt.reset()
   103  }
   104  
   105  // findGROPacket4 returns the groPkt that matches ipHdr and tcpHdr, or nil if
   106  // none exists. It also returns whether the groPkt should be flushed based on
   107  // differences between the two headers.
   108  func (gb *groBucket) findGROPacket4(pkt *stack.PacketBuffer, ipHdr header.IPv4, tcpHdr header.TCP) (*groPacket, bool) {
   109  	for groPkt := gb.packets.Front(); groPkt != nil; groPkt = groPkt.Next() {
   110  		// Do the addresses match?
   111  		groIPHdr := header.IPv4(groPkt.ipHdr)
   112  		if ipHdr.SourceAddress() != groIPHdr.SourceAddress() || ipHdr.DestinationAddress() != groIPHdr.DestinationAddress() {
   113  			continue
   114  		}
   115  
   116  		// Do the ports match?
   117  		if tcpHdr.SourcePort() != groPkt.tcpHdr.SourcePort() || tcpHdr.DestinationPort() != groPkt.tcpHdr.DestinationPort() {
   118  			continue
   119  		}
   120  
   121  		// We've found a packet of the same flow.
   122  
   123  		// IP checks.
   124  		TOS, _ := ipHdr.TOS()
   125  		groTOS, _ := groIPHdr.TOS()
   126  		if ipHdr.TTL() != groIPHdr.TTL() || TOS != groTOS {
   127  			return groPkt, true
   128  		}
   129  
   130  		// TCP checks.
   131  		if shouldFlushTCP(groPkt, tcpHdr) {
   132  			return groPkt, true
   133  		}
   134  
   135  		// There's an upper limit on coalesced packet size.
   136  		if pkt.Data().Size()-header.IPv4MinimumSize-int(tcpHdr.DataOffset())+groPkt.pkt.Data().Size() >= groMaxPacketSize {
   137  			return groPkt, true
   138  		}
   139  
   140  		return groPkt, false
   141  	}
   142  
   143  	return nil, false
   144  }
   145  
   146  // findGROPacket6 returns the groPkt that matches ipHdr and tcpHdr, or nil if
   147  // none exists. It also returns whether the groPkt should be flushed based on
   148  // differences between the two headers.
   149  func (gb *groBucket) findGROPacket6(pkt *stack.PacketBuffer, ipHdr header.IPv6, tcpHdr header.TCP) (*groPacket, bool) {
   150  	for groPkt := gb.packets.Front(); groPkt != nil; groPkt = groPkt.Next() {
   151  		// Do the addresses match?
   152  		groIPHdr := header.IPv6(groPkt.ipHdr)
   153  		if ipHdr.SourceAddress() != groIPHdr.SourceAddress() || ipHdr.DestinationAddress() != groIPHdr.DestinationAddress() {
   154  			continue
   155  		}
   156  
   157  		// Need to check that headers are the same except:
   158  		// - Traffic class, a difference of which causes a flush.
   159  		// - Hop limit, a difference of which causes a flush.
   160  		// - Length, which is checked later.
   161  		// - Version, which is checked by an earlier call to IsValid().
   162  		trafficClass, flowLabel := ipHdr.TOS()
   163  		groTrafficClass, groFlowLabel := groIPHdr.TOS()
   164  		if flowLabel != groFlowLabel || ipHdr.NextHeader() != groIPHdr.NextHeader() {
   165  			continue
   166  		}
   167  		// Unlike IPv4, IPv6 packets with extension headers can be coalesced.
   168  		if !bytes.Equal(ipHdr[header.IPv6MinimumSize:], groIPHdr[header.IPv6MinimumSize:]) {
   169  			continue
   170  		}
   171  
   172  		// Do the ports match?
   173  		if tcpHdr.SourcePort() != groPkt.tcpHdr.SourcePort() || tcpHdr.DestinationPort() != groPkt.tcpHdr.DestinationPort() {
   174  			continue
   175  		}
   176  
   177  		// We've found a packet of the same flow.
   178  
   179  		// TCP checks.
   180  		if shouldFlushTCP(groPkt, tcpHdr) {
   181  			return groPkt, true
   182  		}
   183  
   184  		// Do the traffic class and hop limit match?
   185  		if trafficClass != groTrafficClass || ipHdr.HopLimit() != groIPHdr.HopLimit() {
   186  			return groPkt, true
   187  		}
   188  
   189  		// This limit is artificial for IPv6 -- we could allow even
   190  		// larger packets via jumbograms.
   191  		if pkt.Data().Size()-len(ipHdr)-int(tcpHdr.DataOffset())+groPkt.pkt.Data().Size() >= groMaxPacketSize {
   192  			return groPkt, true
   193  		}
   194  
   195  		return groPkt, false
   196  	}
   197  
   198  	return nil, false
   199  }
   200  
   201  func (gb *groBucket) found(gd *GRO, groPkt *groPacket, flushGROPkt bool, pkt *stack.PacketBuffer, ipHdr []byte, tcpHdr header.TCP, updateIPHdr func([]byte, int)) {
   202  	// Flush groPkt or merge the packets.
   203  	pktSize := pkt.Data().Size()
   204  	flags := tcpHdr.Flags()
   205  	dataOff := tcpHdr.DataOffset()
   206  	tcpPayloadSize := pkt.Data().Size() - len(ipHdr) - int(dataOff)
   207  	if flushGROPkt {
   208  		// Flush the existing GRO packet.
   209  		pkt := groPkt.pkt
   210  		gb.removeOne(groPkt)
   211  		gd.handlePacket(pkt)
   212  		pkt.DecRef()
   213  		groPkt = nil
   214  	} else if groPkt != nil {
   215  		// Merge pkt in to GRO packet.
   216  		pkt.Data().TrimFront(len(ipHdr) + int(dataOff))
   217  		groPkt.pkt.Data().Merge(pkt.Data())
   218  		// Update the IP total length.
   219  		updateIPHdr(groPkt.ipHdr, tcpPayloadSize)
   220  		// Add flags from the packet to the GRO packet.
   221  		groPkt.tcpHdr.SetFlags(uint8(groPkt.tcpHdr.Flags() | (flags & (header.TCPFlagFin | header.TCPFlagPsh))))
   222  
   223  		pkt = nil
   224  	}
   225  
   226  	// Flush if the packet isn't the same size as the previous packets or
   227  	// if certain flags are set. The reason for checking size equality is:
   228  	// - If the packet is smaller than the others, this is likely the end
   229  	//   of some message. Peers will send MSS-sized packets until they have
   230  	//   insufficient data to do so.
   231  	// - If the packet is larger than the others, this packet is either
   232  	//   malformed, a local GSO packet, or has already been handled by host
   233  	//   GRO.
   234  	flush := header.TCPFlags(flags)&(header.TCPFlagUrg|header.TCPFlagPsh|header.TCPFlagRst|header.TCPFlagSyn|header.TCPFlagFin) != 0
   235  	flush = flush || tcpPayloadSize == 0
   236  	if groPkt != nil {
   237  		flush = flush || pktSize != groPkt.initialLength
   238  	}
   239  
   240  	switch {
   241  	case flush && groPkt != nil:
   242  		// A merge occurred and we need to flush groPkt.
   243  		pkt := groPkt.pkt
   244  		gb.removeOne(groPkt)
   245  		gd.handlePacket(pkt)
   246  		pkt.DecRef()
   247  	case flush && groPkt == nil:
   248  		// No merge occurred and the incoming packet needs to be flushed.
   249  		gd.handlePacket(pkt)
   250  	case !flush && groPkt == nil:
   251  		// New flow and we don't need to flush. Insert pkt into GRO.
   252  		if gb.full() {
   253  			// Head is always the oldest packet
   254  			toFlush := gb.removeOldest()
   255  			gb.insert(pkt.IncRef(), ipHdr, tcpHdr)
   256  			gd.handlePacket(toFlush)
   257  			toFlush.DecRef()
   258  		} else {
   259  			gb.insert(pkt.IncRef(), ipHdr, tcpHdr)
   260  		}
   261  	default:
   262  		// A merge occurred and we don't need to flush anything.
   263  	}
   264  }
   265  
   266  // A groPacket is packet undergoing GRO. It may be several packets coalesced
   267  // together.
   268  type groPacket struct {
   269  	// groPacketEntry is an intrusive list.
   270  	groPacketEntry
   271  
   272  	// pkt is the coalesced packet.
   273  	pkt *stack.PacketBuffer
   274  
   275  	// ipHdr is the IP (v4 or v6) header for the coalesced packet.
   276  	ipHdr []byte
   277  
   278  	// tcpHdr is the TCP header for the coalesced packet.
   279  	tcpHdr header.TCP
   280  
   281  	// initialLength is the length of the first packet in the flow. It is
   282  	// used as a best-effort guess at MSS: senders will send MSS-sized
   283  	// packets until they run out of data, so we coalesce as long as
   284  	// packets are the same size.
   285  	initialLength int
   286  
   287  	// idx is the groPacket's index in its bucket packetsPrealloc. It is
   288  	// immutable.
   289  	idx int
   290  }
   291  
   292  // reset resets all mutable fields of the groPacket.
   293  func (pk *groPacket) reset() {
   294  	*pk = groPacket{
   295  		idx: pk.idx,
   296  	}
   297  }
   298  
   299  // payloadSize is the payload size of the coalesced packet, which does not
   300  // include the network or transport headers.
   301  func (pk *groPacket) payloadSize() int {
   302  	return pk.pkt.Data().Size() - len(pk.ipHdr) - int(pk.tcpHdr.DataOffset())
   303  }
   304  
   305  // GRO coalesces incoming packets to increase throughput.
   306  type GRO struct {
   307  	enabled bool
   308  	buckets [groNBuckets]groBucket
   309  
   310  	Dispatcher stack.NetworkDispatcher
   311  }
   312  
   313  // Init initializes GRO.
   314  func (gd *GRO) Init(enabled bool) {
   315  	gd.enabled = enabled
   316  	for i := range gd.buckets {
   317  		bucket := &gd.buckets[i]
   318  		for j := range bucket.packetsPrealloc {
   319  			bucket.allocIdxs[j] = j
   320  			bucket.packetsPrealloc[j].idx = j
   321  		}
   322  	}
   323  }
   324  
   325  // Enqueue the packet in GRO. This does not flush packets; Flush() must be
   326  // called explicitly for that.
   327  //
   328  // pkt.NetworkProtocolNumber and pkt.RXChecksumValidated must be set.
   329  func (gd *GRO) Enqueue(pkt *stack.PacketBuffer) {
   330  	if !gd.enabled {
   331  		gd.handlePacket(pkt)
   332  		return
   333  	}
   334  
   335  	switch pkt.NetworkProtocolNumber {
   336  	case header.IPv4ProtocolNumber:
   337  		gd.dispatch4(pkt)
   338  	case header.IPv6ProtocolNumber:
   339  		gd.dispatch6(pkt)
   340  	default:
   341  		gd.handlePacket(pkt)
   342  	}
   343  }
   344  
   345  func (gd *GRO) dispatch4(pkt *stack.PacketBuffer) {
   346  	// Immediately get the IPv4 and TCP headers. We need a way to hash the
   347  	// packet into its bucket, which requires addresses and ports. Linux
   348  	// simply gets a hash passed by hardware, but we're not so lucky.
   349  
   350  	// We only GRO TCP packets. The check for the transport protocol number
   351  	// is done below so that we can PullUp both the IP and TCP headers
   352  	// together.
   353  	hdrBytes, ok := pkt.Data().PullUp(header.IPv4MinimumSize + header.TCPMinimumSize)
   354  	if !ok {
   355  		gd.handlePacket(pkt)
   356  		return
   357  	}
   358  	ipHdr := header.IPv4(hdrBytes)
   359  
   360  	// We don't handle fragments. That should be the vast majority of
   361  	// traffic, and simplifies handling.
   362  	if ipHdr.FragmentOffset() != 0 || ipHdr.Flags()&header.IPv4FlagMoreFragments != 0 {
   363  		gd.handlePacket(pkt)
   364  		return
   365  	}
   366  
   367  	// We only handle TCP packets without IP options.
   368  	if ipHdr.HeaderLength() != header.IPv4MinimumSize || tcpip.TransportProtocolNumber(ipHdr.Protocol()) != header.TCPProtocolNumber {
   369  		gd.handlePacket(pkt)
   370  		return
   371  	}
   372  	tcpHdr := header.TCP(hdrBytes[header.IPv4MinimumSize:])
   373  	ipHdr = ipHdr[:header.IPv4MinimumSize]
   374  	dataOff := tcpHdr.DataOffset()
   375  	if dataOff < header.TCPMinimumSize {
   376  		// Malformed packet: will be handled further up the stack.
   377  		gd.handlePacket(pkt)
   378  		return
   379  	}
   380  	hdrBytes, ok = pkt.Data().PullUp(header.IPv4MinimumSize + int(dataOff))
   381  	if !ok {
   382  		// Malformed packet: will be handled further up the stack.
   383  		gd.handlePacket(pkt)
   384  		return
   385  	}
   386  
   387  	tcpHdr = header.TCP(hdrBytes[header.IPv4MinimumSize:])
   388  
   389  	// If either checksum is bad, flush the packet. Since we don't know
   390  	// what bits were flipped, we can't identify this packet with a flow.
   391  	if !pkt.RXChecksumValidated {
   392  		if !ipHdr.IsValid(pkt.Data().Size()) || !ipHdr.IsChecksumValid() {
   393  			gd.handlePacket(pkt)
   394  			return
   395  		}
   396  		payloadChecksum := pkt.Data().ChecksumAtOffset(header.IPv4MinimumSize + int(dataOff))
   397  		tcpPayloadSize := pkt.Data().Size() - header.IPv4MinimumSize - int(dataOff)
   398  		if !tcpHdr.IsChecksumValid(ipHdr.SourceAddress(), ipHdr.DestinationAddress(), payloadChecksum, uint16(tcpPayloadSize)) {
   399  			gd.handlePacket(pkt)
   400  			return
   401  		}
   402  		// We've validated the checksum, no reason for others to do it
   403  		// again.
   404  		pkt.RXChecksumValidated = true
   405  	}
   406  
   407  	// Now we can get the bucket for the packet.
   408  	bucket := &gd.buckets[gd.bucketForPacket4(ipHdr, tcpHdr)&groNBucketsMask]
   409  	groPkt, flushGROPkt := bucket.findGROPacket4(pkt, ipHdr, tcpHdr)
   410  	bucket.found(gd, groPkt, flushGROPkt, pkt, ipHdr, tcpHdr, updateIPv4Hdr)
   411  }
   412  
   413  func (gd *GRO) dispatch6(pkt *stack.PacketBuffer) {
   414  	// Immediately get the IPv6 and TCP headers. We need a way to hash the
   415  	// packet into its bucket, which requires addresses and ports. Linux
   416  	// simply gets a hash passed by hardware, but we're not so lucky.
   417  
   418  	hdrBytes, ok := pkt.Data().PullUp(header.IPv6MinimumSize)
   419  	if !ok {
   420  		gd.handlePacket(pkt)
   421  		return
   422  	}
   423  	ipHdr := header.IPv6(hdrBytes)
   424  
   425  	// Getting the IP header (+ extension headers) size is a bit of a pain
   426  	// on IPv6.
   427  	transProto := tcpip.TransportProtocolNumber(ipHdr.NextHeader())
   428  	buf := pkt.Data().ToBuffer()
   429  	buf.TrimFront(header.IPv6MinimumSize)
   430  	it := header.MakeIPv6PayloadIterator(header.IPv6ExtensionHeaderIdentifier(transProto), buf)
   431  	ipHdrSize := int(header.IPv6MinimumSize)
   432  	for {
   433  		transProto = tcpip.TransportProtocolNumber(it.NextHeaderIdentifier())
   434  		extHdr, done, err := it.Next()
   435  		if err != nil {
   436  			gd.handlePacket(pkt)
   437  			return
   438  		}
   439  		if done {
   440  			break
   441  		}
   442  		switch extHdr.(type) {
   443  		// We can GRO these, so just skip over them.
   444  		case header.IPv6HopByHopOptionsExtHdr:
   445  		case header.IPv6RoutingExtHdr:
   446  		case header.IPv6DestinationOptionsExtHdr:
   447  		default:
   448  			// This is either a TCP header or something we can't handle.
   449  			ipHdrSize = int(it.HeaderOffset())
   450  			done = true
   451  		}
   452  		extHdr.Release()
   453  		if done {
   454  			break
   455  		}
   456  	}
   457  
   458  	hdrBytes, ok = pkt.Data().PullUp(ipHdrSize + header.TCPMinimumSize)
   459  	if !ok {
   460  		gd.handlePacket(pkt)
   461  		return
   462  	}
   463  	ipHdr = header.IPv6(hdrBytes[:ipHdrSize])
   464  
   465  	// We only handle TCP packets.
   466  	if transProto != header.TCPProtocolNumber {
   467  		gd.handlePacket(pkt)
   468  		return
   469  	}
   470  	tcpHdr := header.TCP(hdrBytes[ipHdrSize:])
   471  	dataOff := tcpHdr.DataOffset()
   472  	if dataOff < header.TCPMinimumSize {
   473  		// Malformed packet: will be handled further up the stack.
   474  		gd.handlePacket(pkt)
   475  		return
   476  	}
   477  
   478  	hdrBytes, ok = pkt.Data().PullUp(ipHdrSize + int(dataOff))
   479  	if !ok {
   480  		// Malformed packet: will be handled further up the stack.
   481  		gd.handlePacket(pkt)
   482  		return
   483  	}
   484  	tcpHdr = header.TCP(hdrBytes[ipHdrSize:])
   485  
   486  	// If either checksum is bad, flush the packet. Since we don't know
   487  	// what bits were flipped, we can't identify this packet with a flow.
   488  	if !pkt.RXChecksumValidated {
   489  		if !ipHdr.IsValid(pkt.Data().Size()) {
   490  			gd.handlePacket(pkt)
   491  			return
   492  		}
   493  		payloadChecksum := pkt.Data().ChecksumAtOffset(ipHdrSize + int(dataOff))
   494  		tcpPayloadSize := pkt.Data().Size() - ipHdrSize - int(dataOff)
   495  		if !tcpHdr.IsChecksumValid(ipHdr.SourceAddress(), ipHdr.DestinationAddress(), payloadChecksum, uint16(tcpPayloadSize)) {
   496  			gd.handlePacket(pkt)
   497  			return
   498  		}
   499  		// We've validated the checksum, no reason for others to do it
   500  		// again.
   501  		pkt.RXChecksumValidated = true
   502  	}
   503  
   504  	// Now we can get the bucket for the packet.
   505  	bucket := &gd.buckets[gd.bucketForPacket6(ipHdr, tcpHdr)&groNBucketsMask]
   506  	groPkt, flushGROPkt := bucket.findGROPacket6(pkt, ipHdr, tcpHdr)
   507  	bucket.found(gd, groPkt, flushGROPkt, pkt, ipHdr, tcpHdr, updateIPv6Hdr)
   508  }
   509  
   510  func (gd *GRO) bucketForPacket4(ipHdr header.IPv4, tcpHdr header.TCP) int {
   511  	// TODO(b/256037250): Use jenkins or checksum. Write a test to print
   512  	// distribution.
   513  	var sum int
   514  	srcAddr := ipHdr.SourceAddress()
   515  	for _, val := range srcAddr.AsSlice() {
   516  		sum += int(val)
   517  	}
   518  	dstAddr := ipHdr.DestinationAddress()
   519  	for _, val := range dstAddr.AsSlice() {
   520  		sum += int(val)
   521  	}
   522  	sum += int(tcpHdr.SourcePort())
   523  	sum += int(tcpHdr.DestinationPort())
   524  	return sum
   525  }
   526  
   527  func (gd *GRO) bucketForPacket6(ipHdr header.IPv6, tcpHdr header.TCP) int {
   528  	// TODO(b/256037250): Use jenkins or checksum. Write a test to print
   529  	// distribution.
   530  	var sum int
   531  	srcAddr := ipHdr.SourceAddress()
   532  	for _, val := range srcAddr.AsSlice() {
   533  		sum += int(val)
   534  	}
   535  	dstAddr := ipHdr.DestinationAddress()
   536  	for _, val := range dstAddr.AsSlice() {
   537  		sum += int(val)
   538  	}
   539  	sum += int(tcpHdr.SourcePort())
   540  	sum += int(tcpHdr.DestinationPort())
   541  	return sum
   542  }
   543  
   544  // Flush sends all packets up the stack.
   545  func (gd *GRO) Flush() {
   546  	for i := range gd.buckets {
   547  		for groPkt := gd.buckets[i].packets.Front(); groPkt != nil; groPkt = groPkt.Next() {
   548  			pkt := groPkt.pkt
   549  			gd.buckets[i].removeOne(groPkt)
   550  			gd.handlePacket(pkt)
   551  			pkt.DecRef()
   552  		}
   553  	}
   554  }
   555  
   556  func (gd *GRO) handlePacket(pkt *stack.PacketBuffer) {
   557  	gd.Dispatcher.DeliverNetworkPacket(pkt.NetworkProtocolNumber, pkt)
   558  }
   559  
   560  // String implements fmt.Stringer.
   561  func (gd *GRO) String() string {
   562  	ret := "GRO state: \n"
   563  	for i := range gd.buckets {
   564  		bucket := &gd.buckets[i]
   565  		ret += fmt.Sprintf("bucket %d: %d packets: ", i, bucket.count)
   566  		for groPkt := bucket.packets.Front(); groPkt != nil; groPkt = groPkt.Next() {
   567  			ret += fmt.Sprintf("%d, ", groPkt.pkt.Data().Size())
   568  		}
   569  		ret += "\n"
   570  	}
   571  	return ret
   572  }
   573  
   574  // shouldFlushTCP returns whether the TCP headers indicate that groPkt should
   575  // be flushed
   576  func shouldFlushTCP(groPkt *groPacket, tcpHdr header.TCP) bool {
   577  	flags := tcpHdr.Flags()
   578  	groPktFlags := groPkt.tcpHdr.Flags()
   579  	dataOff := tcpHdr.DataOffset()
   580  	if flags&header.TCPFlagCwr != 0 || // Is congestion control occurring?
   581  		(flags^groPktFlags)&^(header.TCPFlagCwr|header.TCPFlagFin|header.TCPFlagPsh) != 0 || // Do the flags differ besides CRW, FIN, and PSH?
   582  		tcpHdr.AckNumber() != groPkt.tcpHdr.AckNumber() || // Do the ACKs match?
   583  		dataOff != groPkt.tcpHdr.DataOffset() || // Are the TCP headers the same length?
   584  		groPkt.tcpHdr.SequenceNumber()+uint32(groPkt.payloadSize()) != tcpHdr.SequenceNumber() { // Does the incoming packet match the expected sequence number?
   585  		return true
   586  	}
   587  	// The options, including timestamps, must be identical.
   588  	return !bytes.Equal(tcpHdr[header.TCPMinimumSize:], groPkt.tcpHdr[header.TCPMinimumSize:])
   589  }
   590  
   591  func updateIPv4Hdr(ipHdrBytes []byte, newBytes int) {
   592  	ipHdr := header.IPv4(ipHdrBytes)
   593  	ipHdr.SetTotalLength(ipHdr.TotalLength() + uint16(newBytes))
   594  }
   595  
   596  func updateIPv6Hdr(ipHdrBytes []byte, newBytes int) {
   597  	ipHdr := header.IPv6(ipHdrBytes)
   598  	ipHdr.SetPayloadLength(ipHdr.PayloadLength() + uint16(newBytes))
   599  }