Refactor netstack to use bufferv2 instead of buffer.

This change has significant performance implications. bufferv2 is reference
counted and pooled, which alleviates heap/GC pressure. Below are the results
from running the iperf benchmark.

HEAD:
BenchmarkIperf/operation.Upload-16    1552  ns/op   46.6GiB total allocations
BenchmarkIperf/operation.Download-16  1114  ns/op   68.6GiB total allocations

w/ change:
BenchmarkIperf/operation.Upload-16    1139  ns/op (-27%)   1.41GiB total allocations (-97%)
BenchmarkIperf/operation.Download-16  753.2 ns/op (-33%)   706MiB  total allocations (-99%)

PiperOrigin-RevId: 462453185
This commit is contained in:
Lucas Manning
2022-07-21 13:09:31 -07:00
committed by gVisor bot
parent da267f435f
commit 1f2b30d70c
137 changed files with 2603 additions and 2089 deletions
+1 -1
View File
@@ -36,7 +36,7 @@ _templates:
./pkg/tcpip/transport/icmp
./pkg/tcpip/transport/tcp
./pkg/tcpip/transport/udp
./pkg/buffer
./pkg/bufferv2
./pkg/waiter
env:
# Force a clean checkout every time to avoid reuse of files between runs.
+8 -2
View File
@@ -206,6 +206,10 @@ func (b *Buffer) Prepend(src *View) error {
if src == nil {
return nil
}
if src.Size() == 0 {
src.Release()
return nil
}
// If the first buffer does not have room just prepend the view.
v := b.data.Front()
if v == nil || v.read == 0 {
@@ -251,6 +255,10 @@ func (b *Buffer) Append(src *View) error {
if src == nil {
return nil
}
if src.Size() == 0 {
src.Release()
return nil
}
// If the last buffer is full, just append the view.
v := b.data.Back()
if v.Full() {
@@ -377,8 +385,6 @@ func (b *Buffer) PullUp(offset, length int) (View, bool) {
func (b *Buffer) Flatten() []byte {
if v := b.data.Front(); v == nil {
return nil // No data at all.
} else if v.Next() == nil {
return v.AsSlice() // Only one buffer.
}
data := make([]byte, 0, b.size) // Need to flatten.
for v := b.data.Front(); v != nil; v = v.Next() {
+3 -4
View File
@@ -14,13 +14,12 @@
package bufferv2
// saveBuf is invoked by stateify.
// saveData is invoked by stateify.
func (b *Buffer) saveData() []byte {
return b.Flatten()
}
// loadBuf is invoked by stateify.
// loadData is invoked by stateify.
func (b *Buffer) loadData(data []byte) {
v := NewViewWithData(data)
b.Append(v)
*b = MakeWithData(data)
}
+2
View File
@@ -77,6 +77,8 @@ func getChunkPool(size int) *sync.Pool {
}
// Chunk represents a slice of pooled memory.
//
// +stateify savable
type chunk struct {
chunkRefs
data []byte
+26 -5
View File
@@ -45,11 +45,13 @@ var viewPool = sync.Pool{
// Users must not write directly to slices returned by AsSlice. Instead, they
// must use Write/WriteAt/CopyIn to modify the underlying View. This preserves
// the safety guarantees of copy-on-write.
//
// +stateify savable
type View struct {
viewEntry
read int
write int
chunk *chunk
viewEntry `state:"nosave"`
read int
write int
chunk *chunk
}
// NewView creates a new view with capacity at least as big as cap. It is
@@ -107,6 +109,15 @@ func (v *View) Release() {
viewPool.Put(v)
}
// Reset sets the view's read and write indices back to zero.
func (v *View) Reset() {
if v == nil {
panic("cannot reset a nil view")
}
v.read = 0
v.write = 0
}
func (v *View) sharesChunk() bool {
return v.chunk.refCount.Load() > 1
}
@@ -144,7 +155,7 @@ func (v *View) TrimFront(n int) {
// AsSlice returns a slice of the data written to this view.
func (v *View) AsSlice() []byte {
if v == nil {
if v.Size() == 0 {
return nil
}
return v.chunk.data[v.read:v.write]
@@ -173,6 +184,16 @@ func (v *View) Read(p []byte) (int, error) {
return n, nil
}
// ReadByte implements the io.ByteReader interface.
func (v *View) ReadByte() (byte, error) {
if v.Size() == 0 {
return 0, io.EOF
}
b := v.AsSlice()[0]
v.read++
return b, nil
}
// WriteTo writes data to w until the view is empty or an error occurs. The
// return value n is the number of bytes written.
//
+1
View File
@@ -8,6 +8,7 @@ go_library(
visibility = ["//pkg/sentry:internal"],
deps = [
"//pkg/abi/linux",
"//pkg/bufferv2",
"//pkg/context",
"//pkg/errors/linuxerr",
"//pkg/hostarch",
+10 -4
View File
@@ -16,8 +16,11 @@
package tundev
import (
"io"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/bufferv2"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/hostarch"
@@ -125,8 +128,11 @@ func (fd *tunFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.Read
if err != nil {
return 0, err
}
n, err := dst.CopyOut(ctx, data)
if n > 0 && n < len(data) {
defer data.Release()
size := data.Size()
n, err := io.CopyN(dst.Writer(ctx), data, dst.NumBytes())
if n > 0 && n < int64(size) {
// Not an error for partial copying. Packet truncated.
err = nil
}
@@ -150,8 +156,8 @@ func (fd *tunFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.Wri
if int64(mtu) < src.NumBytes() {
return 0, unix.EMSGSIZE
}
data := make([]byte, src.NumBytes())
if _, err := src.CopyIn(ctx, data); err != nil {
data := bufferv2.NewView(int(src.NumBytes()))
if _, err := io.CopyN(data, src.Reader(ctx), src.NumBytes()); err != nil {
return 0, err
}
return fd.device.Write(data)
+1
View File
@@ -17,6 +17,7 @@ go_library(
visibility = ["//pkg/sentry:internal"],
deps = [
"//pkg/abi/linux",
"//pkg/bufferv2",
"//pkg/context",
"//pkg/errors/linuxerr",
"//pkg/hostarch",
+9 -4
View File
@@ -15,8 +15,11 @@
package dev
import (
"io"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/bufferv2"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/hostarch"
@@ -135,8 +138,8 @@ func (n *netTunFileOperations) Write(ctx context.Context, file *fs.File, src use
if src.NumBytes() == 0 {
return 0, unix.EINVAL
}
data := make([]byte, src.NumBytes())
if _, err := src.CopyIn(ctx, data); err != nil {
data := bufferv2.NewView(int(src.NumBytes()))
if _, err := io.CopyN(data, src.Reader(ctx), src.NumBytes()); err != nil {
return 0, err
}
return n.device.Write(data)
@@ -148,8 +151,10 @@ func (n *netTunFileOperations) Read(ctx context.Context, file *fs.File, dst user
if err != nil {
return 0, err
}
bytesCopied, err := dst.CopyOut(ctx, data)
if bytesCopied > 0 && bytesCopied < len(data) {
defer data.Release()
dataSize := data.Size()
bytesCopied, err := io.CopyN(dst.Writer(ctx), data, dst.NumBytes())
if bytesCopied > 0 && bytesCopied < int64(dataSize) {
// Not an error for partial copying. Packet truncated.
err = nil
}
+3 -3
View File
@@ -98,7 +98,7 @@ func (*TCPMatcher) name() string {
func (tm *TCPMatcher) Match(hook stack.Hook, pkt *stack.PacketBuffer, _, _ string) (bool, bool) {
switch pkt.NetworkProtocolNumber {
case header.IPv4ProtocolNumber:
netHeader := header.IPv4(pkt.NetworkHeader().View())
netHeader := header.IPv4(pkt.NetworkHeader().Slice())
if netHeader.TransportProtocol() != header.TCPProtocolNumber {
return false, false
}
@@ -115,7 +115,7 @@ func (tm *TCPMatcher) Match(hook stack.Hook, pkt *stack.PacketBuffer, _, _ strin
// As in Linux, we do not perform an IPv6 fragment check. See
// xt_action_param.fragoff in
// include/linux/netfilter/x_tables.h.
if header.IPv6(pkt.NetworkHeader().View()).TransportProtocol() != header.TCPProtocolNumber {
if header.IPv6(pkt.NetworkHeader().Slice()).TransportProtocol() != header.TCPProtocolNumber {
return false, false
}
@@ -124,7 +124,7 @@ func (tm *TCPMatcher) Match(hook stack.Hook, pkt *stack.PacketBuffer, _, _ strin
return false, false
}
tcpHeader := header.TCP(pkt.TransportHeader().View())
tcpHeader := header.TCP(pkt.TransportHeader().Slice())
if len(tcpHeader) < header.TCPMinimumSize {
// There's no valid TCP header here, so we drop the packet immediately.
return false, true
+3 -3
View File
@@ -95,7 +95,7 @@ func (*UDPMatcher) name() string {
func (um *UDPMatcher) Match(hook stack.Hook, pkt *stack.PacketBuffer, _, _ string) (bool, bool) {
switch pkt.NetworkProtocolNumber {
case header.IPv4ProtocolNumber:
netHeader := header.IPv4(pkt.NetworkHeader().View())
netHeader := header.IPv4(pkt.NetworkHeader().Slice())
if netHeader.TransportProtocol() != header.UDPProtocolNumber {
return false, false
}
@@ -112,7 +112,7 @@ func (um *UDPMatcher) Match(hook stack.Hook, pkt *stack.PacketBuffer, _, _ strin
// As in Linux, we do not perform an IPv6 fragment check. See
// xt_action_param.fragoff in
// include/linux/netfilter/x_tables.h.
if header.IPv6(pkt.NetworkHeader().View()).TransportProtocol() != header.UDPProtocolNumber {
if header.IPv6(pkt.NetworkHeader().Slice()).TransportProtocol() != header.UDPProtocolNumber {
return false, false
}
@@ -121,7 +121,7 @@ func (um *UDPMatcher) Match(hook stack.Hook, pkt *stack.PacketBuffer, _, _ strin
return false, false
}
udpHeader := header.UDP(pkt.TransportHeader().View())
udpHeader := header.UDP(pkt.TransportHeader().Slice())
if len(udpHeader) < header.UDPMinimumSize {
// There's no valid UDP header here, so we drop the packet immediately.
return false, true
+5 -2
View File
@@ -3001,14 +3001,17 @@ func (s *socketOpsCommon) recvErr(t *kernel.Task, dst usermem.IOSequence) (int,
if sockErr == nil {
return 0, 0, nil, 0, socket.ControlMessages{}, syserr.ErrTryAgain
}
if sockErr.Payload != nil {
defer sockErr.Payload.Release()
}
// The payload of the original packet that caused the error is passed as
// normal data via msg_iovec. -- recvmsg(2)
msgFlags := linux.MSG_ERRQUEUE
if int(dst.NumBytes()) < len(sockErr.Payload) {
if int(dst.NumBytes()) < sockErr.Payload.Size() {
msgFlags |= linux.MSG_TRUNC
}
n, err := dst.CopyOut(t, sockErr.Payload)
n, err := dst.CopyOut(t, sockErr.Payload.AsSlice())
// The original destination address of the datagram that caused the error is
// supplied via msg_name. -- recvmsg(2)
+1 -1
View File
@@ -94,7 +94,7 @@ go_library(
deps = [
"//pkg/abi/linux",
"//pkg/atomicbitops",
"//pkg/buffer",
"//pkg/bufferv2",
"//pkg/context",
"//pkg/errors/linuxerr",
"//pkg/fdnotifier",
+4 -1
View File
@@ -31,6 +31,7 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//pkg/atomicbitops",
"//pkg/bufferv2",
"//pkg/sync",
"//pkg/waiter",
],
@@ -47,14 +48,16 @@ deps_test(
allowed = [
# gVisor deps.
"//pkg/atomicbitops",
"//pkg/bits",
"//pkg/context",
"//pkg/buffer",
"//pkg/bufferv2",
"//pkg/cpuid",
"//pkg/gohacks",
"//pkg/goid",
"//pkg/ilist",
"//pkg/linewriter",
"//pkg/log",
"//pkg/pool",
"//pkg/rand",
"//pkg/refs",
"//pkg/refsvfs2",
+1 -1
View File
@@ -8,7 +8,7 @@ go_library(
srcs = ["checker.go"],
visibility = ["//visibility:public"],
deps = [
"//pkg/buffer",
"//pkg/bufferv2",
"//pkg/tcpip",
"//pkg/tcpip/header",
"//pkg/tcpip/seqnum",
+25 -13
View File
@@ -23,7 +23,7 @@ import (
"time"
"github.com/google/go-cmp/cmp"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/bufferv2"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/seqnum"
@@ -43,13 +43,13 @@ type ControlMessagesChecker func(*testing.T, tcpip.ReceivableControlMessages)
// properties. For example, to check the source and destination address, one
// would call:
//
// checker.IPv4(t, b, checker.SrcAddr(x), checker.DstAddr(y))
func IPv4(t *testing.T, b []byte, checkers ...NetworkChecker) {
// checker.IPv4(t, v, checker.SrcAddr(x), checker.DstAddr(y))
func IPv4(t *testing.T, v *bufferv2.View, checkers ...NetworkChecker) {
t.Helper()
ipv4 := header.IPv4(b)
ipv4 := header.IPv4(v.AsSlice())
if !ipv4.IsValid(len(b)) {
if !ipv4.IsValid(len(v.AsSlice())) {
t.Fatalf("Not a valid IPv4 packet: %x", ipv4)
}
@@ -67,11 +67,11 @@ func IPv4(t *testing.T, b []byte, checkers ...NetworkChecker) {
// IPv6 checks the validity and properties of the given IPv6 packet. The usage
// is similar to IPv4.
func IPv6(t *testing.T, b []byte, checkers ...NetworkChecker) {
func IPv6(t *testing.T, v *bufferv2.View, checkers ...NetworkChecker) {
t.Helper()
ipv6 := header.IPv6(b)
if !ipv6.IsValid(len(b)) {
ipv6 := header.IPv6(v.AsSlice())
if !ipv6.IsValid(len(v.AsSlice())) {
t.Fatalf("Not a valid IPv6 packet: %x", ipv6)
}
@@ -1535,19 +1535,20 @@ func IGMPGroupAddress(want tcpip.Address) TransportChecker {
type IPv6ExtHdrChecker func(*testing.T, header.IPv6PayloadHeader)
// IPv6WithExtHdr is like IPv6 but allows IPv6 packets with extension headers.
func IPv6WithExtHdr(t *testing.T, b []byte, checkers ...NetworkChecker) {
func IPv6WithExtHdr(t *testing.T, v *bufferv2.View, checkers ...NetworkChecker) {
t.Helper()
ipv6 := header.IPv6(b)
if !ipv6.IsValid(len(b)) {
ipv6 := header.IPv6(v.AsSlice())
if !ipv6.IsValid(len(v.AsSlice())) {
t.Error("not a valid IPv6 packet")
return
}
payloadIterator := header.MakeIPv6PayloadIterator(
header.IPv6ExtensionHeaderIdentifier(ipv6.NextHeader()),
buffer.NewWithData(ipv6.Payload()),
bufferv2.MakeWithData(ipv6.Payload()),
)
defer payloadIterator.Release()
var rawPayloadHeader header.IPv6RawPayloadHeader
for {
@@ -1560,6 +1561,7 @@ func IPv6WithExtHdr(t *testing.T, b []byte, checkers ...NetworkChecker) {
t.Errorf("got payloadIterator.Next() = (%T, %t, _), want = (_, true, _)", h, done)
return
}
defer h.Release()
r, ok := h.(header.IPv6RawPayloadHeader)
if ok {
rawPayloadHeader = r
@@ -1594,8 +1596,9 @@ func IPv6ExtHdr(headers ...IPv6ExtHdrChecker) NetworkChecker {
payloadIterator := header.MakeIPv6PayloadIterator(
header.IPv6ExtensionHeaderIdentifier(extHdrs.IPv6.NextHeader()),
buffer.NewWithData(extHdrs.IPv6.Payload()),
bufferv2.MakeWithData(extHdrs.IPv6.Payload()),
)
defer payloadIterator.Release()
for _, check := range headers {
h, done, err := payloadIterator.Next()
@@ -1608,6 +1611,7 @@ func IPv6ExtHdr(headers ...IPv6ExtHdrChecker) NetworkChecker {
return
}
check(t, h)
h.Release()
}
// Validate we consumed all headers.
//
@@ -1630,6 +1634,8 @@ func IPv6ExtHdr(headers ...IPv6ExtHdrChecker) NetworkChecker {
if _, ok := h.(header.IPv6RawPayloadHeader); !ok {
t.Errorf("got payloadIterator.Next() = (%T, _, _), want = (header.IPv6RawPayloadHeader, _, _)", h)
continue
} else {
h.Release()
}
wantDone = true
}
@@ -1684,6 +1690,9 @@ func IPv6HopByHopExtensionHeader(checkers ...IPv6ExtHdrOptionChecker) IPv6ExtHdr
t.Errorf("got optionsIterator.Next() = (%T, %t, _), want = (_, false, _)", opt, done)
}
f(t, opt)
if uo, ok := opt.(*header.IPv6UnknownExtHdrOption); ok {
uo.Data.Release()
}
}
// Validate all options were consumed.
for {
@@ -1698,6 +1707,9 @@ func IPv6HopByHopExtensionHeader(checkers ...IPv6ExtHdrOptionChecker) IPv6ExtHdr
if done {
break
}
if uo, ok := opt.(*header.IPv6UnknownExtHdrOption); ok {
uo.Data.Release()
}
}
}
}
+3 -3
View File
@@ -30,7 +30,7 @@ go_library(
],
visibility = ["//visibility:public"],
deps = [
"//pkg/buffer",
"//pkg/bufferv2",
"//pkg/tcpip",
"//pkg/tcpip/seqnum",
"@com_github_google_btree//:go_default_library",
@@ -50,7 +50,7 @@ go_test(
],
deps = [
":header",
"//pkg/buffer",
"//pkg/bufferv2",
"//pkg/rand",
"//pkg/tcpip",
"//pkg/tcpip/prependable",
@@ -70,7 +70,7 @@ go_test(
],
library = ":header",
deps = [
"//pkg/buffer",
"//pkg/bufferv2",
"//pkg/tcpip",
"//pkg/tcpip/testutil",
"@com_github_google_go_cmp//cmp:go_default_library",
+4 -4
View File
@@ -20,7 +20,7 @@ import (
"encoding/binary"
"fmt"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/bufferv2"
"gvisor.dev/gvisor/pkg/tcpip"
)
@@ -196,10 +196,10 @@ func Checksum(buf []byte, initial uint16) uint16 {
// bytes in the given Buffer.
//
// The initial checksum must have been computed on an even number of bytes.
func ChecksumBuffer(buf buffer.Buffer, initial uint16) uint16 {
func ChecksumBuffer(buf bufferv2.Buffer, initial uint16) uint16 {
var c Checksumer
buf.Apply(func(b []byte) {
c.Add(b)
buf.Apply(func(v *bufferv2.View) {
c.Add(v.AsSlice())
})
return ChecksumCombine(initial, c.Checksum())
}
+8 -6
View File
@@ -23,7 +23,7 @@ import (
"sync"
"testing"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/bufferv2"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
)
@@ -193,6 +193,8 @@ func testICMPChecksum(t *testing.T, headerChecksum func() uint16, icmpChecksum f
close(start)
}
// TODO(b/239732156): Replace magic constants with names corresponding to what
// they represent ICMP.
func TestICMPv4Checksum(t *testing.T) {
rnd := rand.New(rand.NewSource(42))
@@ -206,8 +208,8 @@ func TestICMPv4Checksum(t *testing.T) {
if _, err := rnd.Read(buf); err != nil {
t.Fatalf("rnd.Read failed: %v", err)
}
b := buffer.NewWithData(buf[:5])
b.AppendOwned(buf[5:])
b := bufferv2.MakeWithData(buf[:5])
b.Append(bufferv2.NewViewWithData(buf[5:]))
want := header.Checksum(b.Flatten(), 0)
want = ^header.Checksum(h, want)
@@ -231,9 +233,9 @@ func TestICMPv6Checksum(t *testing.T) {
if _, err := rnd.Read(buf); err != nil {
t.Fatalf("rnd.Read failed: %v", err)
}
b := buffer.NewWithData(buf[:7])
b.AppendOwned(buf[7:10])
b.AppendOwned(buf[10:])
b := bufferv2.MakeWithData(buf[:7])
b.Append(bufferv2.NewViewWithData(buf[7:10]))
b.Append(bufferv2.NewViewWithData(buf[10:]))
dst := header.IPv6Loopback
src := header.IPv6Loopback
+84 -62
View File
@@ -15,15 +15,13 @@
package header
import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/bufferv2"
"gvisor.dev/gvisor/pkg/tcpip"
)
@@ -154,26 +152,44 @@ func ipv6OptionsAlignmentPadding(headerOffset int, align int, alignOffset int) i
// These headers include IPv6 extension headers or upper layer data.
type IPv6PayloadHeader interface {
isIPv6PayloadHeader()
// Release frees all resources held by the header.
Release()
}
// IPv6RawPayloadHeader the remainder of an IPv6 payload after an iterator
// encounters a Next Header field it does not recognize as an IPv6 extension
// header.
// header. The caller is responsible for releasing the underlying buffer after
// it's no longer needed.
type IPv6RawPayloadHeader struct {
Identifier IPv6ExtensionHeaderIdentifier
Buf buffer.Buffer
Buf bufferv2.Buffer
}
// isIPv6PayloadHeader implements IPv6PayloadHeader.isIPv6PayloadHeader.
func (IPv6RawPayloadHeader) isIPv6PayloadHeader() {}
// Release implements IPv6PayloadHeader.Release.
func (i IPv6RawPayloadHeader) Release() {
i.Buf.Release()
}
// ipv6OptionsExtHdr is an IPv6 extension header that holds options.
type ipv6OptionsExtHdr []byte
type ipv6OptionsExtHdr struct {
buf *bufferv2.View
}
// Release implements IPv6PayloadHeader.Release.
func (i ipv6OptionsExtHdr) Release() {
if i.buf != nil {
i.buf.Release()
}
}
// Iter returns an iterator over the IPv6 extension header options held in b.
func (b ipv6OptionsExtHdr) Iter() IPv6OptionsExtHdrOptionsIterator {
func (i ipv6OptionsExtHdr) Iter() IPv6OptionsExtHdrOptionsIterator {
it := IPv6OptionsExtHdrOptionsIterator{}
it.reader.Reset(b)
it.reader = i.buf
return it
}
@@ -187,7 +203,7 @@ func (b ipv6OptionsExtHdr) Iter() IPv6OptionsExtHdrOptionsIterator {
// modify the backing payload so long as the IPv6OptionsExtHdrOptionsIterator
// obtained before modification is no longer used.
type IPv6OptionsExtHdrOptionsIterator struct {
reader bytes.Reader
reader *bufferv2.View
// optionOffset is the number of bytes from the first byte of the
// options field to the beginning of the current option.
@@ -283,7 +299,7 @@ var ErrMalformedIPv6ExtHdrOption = errors.New("malformed IPv6 extension header o
// header option that is unknown by the parsing utilities.
type IPv6UnknownExtHdrOption struct {
Identifier IPv6ExtHdrOptionIdentifier
Data []byte
Data *bufferv2.View
}
// UnknownAction implements IPv6OptionUnknownAction.UnknownAction.
@@ -335,9 +351,9 @@ func (i *IPv6OptionsExtHdrOptionsIterator) Next() (IPv6ExtHdrOption, bool, error
}
// Do we have enough bytes in the reader for the next option?
if n := i.reader.Len(); n < int(length) {
// Reset the reader to effectively consume the remaining buffer.
i.reader.Reset(nil)
if n := i.reader.Size(); n < int(length) {
// Consume the remaining buffer.
i.reader.TrimFront(i.reader.Size())
// We return the same error as if we failed to read a non-padding option
// so consumers of this iterator don't need to differentiate between
@@ -350,13 +366,11 @@ func (i *IPv6OptionsExtHdrOptionsIterator) Next() (IPv6ExtHdrOption, bool, error
switch id {
case ipv6PadNExtHdrOptionIdentifier:
// Special-case the variable length padding option to avoid a copy.
if _, err := i.reader.Seek(int64(length), io.SeekCurrent); err != nil {
panic(fmt.Sprintf("error when skipping PadN (N = %d) option's data bytes: %s", length, err))
}
i.reader.TrimFront(int(length))
continue
case ipv6RouterAlertHopByHopOptionIdentifier:
var routerAlertValue [ipv6RouterAlertPayloadLength]byte
if n, err := io.ReadFull(&i.reader, routerAlertValue[:]); err != nil {
if n, err := io.ReadFull(i.reader, routerAlertValue[:]); err != nil {
switch err {
case io.EOF, io.ErrUnexpectedEOF:
return nil, true, fmt.Errorf("got invalid length (%d) for router alert option (want = %d): %w", length, ipv6RouterAlertPayloadLength, ErrMalformedIPv6ExtHdrOption)
@@ -368,11 +382,8 @@ func (i *IPv6OptionsExtHdrOptionsIterator) Next() (IPv6ExtHdrOption, bool, error
}
return &IPv6RouterAlertOption{Value: IPv6RouterAlertValue(binary.BigEndian.Uint16(routerAlertValue[:]))}, false, nil
default:
bytes := make([]byte, length)
if n, err := io.ReadFull(&i.reader, bytes); err != nil {
// io.ReadFull may return io.EOF if i.reader has been exhausted. We use
// io.ErrUnexpectedEOF instead as the io.EOF is unexpected given the
// Length field found in the option.
bytes := bufferv2.NewView(int(length))
if n, err := io.CopyN(bytes, i.reader, int64(length)); err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
@@ -404,14 +415,21 @@ func (IPv6DestinationOptionsExtHdr) isIPv6PayloadHeader() {}
// IPv6RoutingExtHdr is a buffer holding the Routing extension header specific
// data as outlined in RFC 8200 section 4.4.
type IPv6RoutingExtHdr []byte
type IPv6RoutingExtHdr struct {
Buf *bufferv2.View
}
// isIPv6PayloadHeader implements IPv6PayloadHeader.isIPv6PayloadHeader.
func (IPv6RoutingExtHdr) isIPv6PayloadHeader() {}
// Release implements IPv6PayloadHeader.Release.
func (b IPv6RoutingExtHdr) Release() {
b.Buf.Release()
}
// SegmentsLeft returns the Segments Left field.
func (b IPv6RoutingExtHdr) SegmentsLeft() uint8 {
return b[ipv6RoutingExtHdrSegmentsLeftIdx]
return b.Buf.AsSlice()[ipv6RoutingExtHdrSegmentsLeftIdx]
}
// IPv6FragmentExtHdr is a buffer holding the Fragment extension header specific
@@ -423,6 +441,9 @@ type IPv6FragmentExtHdr [6]byte
// isIPv6PayloadHeader implements IPv6PayloadHeader.isIPv6PayloadHeader.
func (IPv6FragmentExtHdr) isIPv6PayloadHeader() {}
// Release implements IPv6PayloadHeader.Release.
func (IPv6FragmentExtHdr) Release() {}
// FragmentOffset returns the Fragment Offset field.
//
// This value indicates where the buffer following the Fragment extension header
@@ -467,9 +488,7 @@ type IPv6PayloadIterator struct {
// The identifier of the next header to parse.
nextHdrIdentifier IPv6ExtensionHeaderIdentifier
// reader is an io.Reader over payload.
reader bufio.Reader
payload buffer.Buffer
payload bufferv2.Buffer
// Indicates to the iterator that it should return the remaining payload as a
// raw payload on the next call to Next.
@@ -500,32 +519,31 @@ func (i IPv6PayloadIterator) ParseOffset() uint32 {
}
// MakeIPv6PayloadIterator returns an iterator over the IPv6 payload containing
// extension headers, or a raw payload if the payload cannot be parsed.
func MakeIPv6PayloadIterator(nextHdrIdentifier IPv6ExtensionHeaderIdentifier, payload buffer.Buffer) IPv6PayloadIterator {
readers := payload.Readers()
readerPs := make([]io.Reader, 0, len(readers))
for i := range readers {
readerPs = append(readerPs, &readers[i])
}
// extension headers, or a raw payload if the payload cannot be parsed. The
// iterator takes ownership of the payload.
func MakeIPv6PayloadIterator(nextHdrIdentifier IPv6ExtensionHeaderIdentifier, payload bufferv2.Buffer) IPv6PayloadIterator {
return IPv6PayloadIterator{
nextHdrIdentifier: nextHdrIdentifier,
payload: payload.Clone(),
// We need a buffer of size 1 for calls to bufio.Reader.ReadByte.
reader: *bufio.NewReaderSize(io.MultiReader(readerPs...), 1),
nextOffset: IPv6FixedHeaderSize,
payload: payload,
nextOffset: IPv6FixedHeaderSize,
}
}
// Release frees the resources owned by the iterator.
func (i *IPv6PayloadIterator) Release() {
i.payload.Release()
}
// AsRawHeader returns the remaining payload of i as a raw header and
// optionally consumes the iterator.
//
// If consume is true, calls to Next after calling AsRawHeader on i will
// indicate that the iterator is done.
// indicate that the iterator is done. The returned header takes ownership of
// its payload.
func (i *IPv6PayloadIterator) AsRawHeader(consume bool) IPv6RawPayloadHeader {
identifier := i.nextHdrIdentifier
var buf buffer.Buffer
var buf bufferv2.Buffer
if consume {
// Since we consume the iterator, we return the payload as is.
buf = i.payload
@@ -564,21 +582,21 @@ func (i *IPv6PayloadIterator) Next() (IPv6PayloadHeader, bool, error) {
// Is the header we are parsing a known extension header?
switch i.nextHdrIdentifier {
case IPv6HopByHopOptionsExtHdrIdentifier:
nextHdrIdentifier, bytes, err := i.nextHeaderData(false /* fragmentHdr */, nil)
nextHdrIdentifier, view, err := i.nextHeaderData(false /* fragmentHdr */, nil)
if err != nil {
return nil, true, err
}
i.nextHdrIdentifier = nextHdrIdentifier
return IPv6HopByHopOptionsExtHdr{ipv6OptionsExtHdr: bytes}, false, nil
return IPv6HopByHopOptionsExtHdr{ipv6OptionsExtHdr{view}}, false, nil
case IPv6RoutingExtHdrIdentifier:
nextHdrIdentifier, bytes, err := i.nextHeaderData(false /* fragmentHdr */, nil)
nextHdrIdentifier, view, err := i.nextHeaderData(false /* fragmentHdr */, nil)
if err != nil {
return nil, true, err
}
i.nextHdrIdentifier = nextHdrIdentifier
return IPv6RoutingExtHdr(bytes), false, nil
return IPv6RoutingExtHdr{view}, false, nil
case IPv6FragmentExtHdrIdentifier:
var data [6]byte
// We ignore the returned bytes because we know the fragment extension
@@ -602,13 +620,13 @@ func (i *IPv6PayloadIterator) Next() (IPv6PayloadHeader, bool, error) {
i.nextHdrIdentifier = nextHdrIdentifier
return fragmentExtHdr, false, nil
case IPv6DestinationOptionsExtHdrIdentifier:
nextHdrIdentifier, bytes, err := i.nextHeaderData(false /* fragmentHdr */, nil)
nextHdrIdentifier, view, err := i.nextHeaderData(false /* fragmentHdr */, nil)
if err != nil {
return nil, true, err
}
i.nextHdrIdentifier = nextHdrIdentifier
return IPv6DestinationOptionsExtHdr{ipv6OptionsExtHdr: bytes}, false, nil
return IPv6DestinationOptionsExtHdr{ipv6OptionsExtHdr{view}}, false, nil
case IPv6NoNextHeaderIdentifier:
// This indicates the end of the IPv6 payload.
return nil, true, nil
@@ -629,21 +647,20 @@ func (i *IPv6PayloadIterator) Next() (IPv6PayloadHeader, bool, error) {
// If bytes is not nil, extension header specific data will be read into bytes
// if it has enough capacity. If bytes is provided but does not have enough
// capacity for the data, nextHeaderData will panic.
func (i *IPv6PayloadIterator) nextHeaderData(fragmentHdr bool, bytes []byte) (IPv6ExtensionHeaderIdentifier, []byte, error) {
func (i *IPv6PayloadIterator) nextHeaderData(fragmentHdr bool, bytes []byte) (IPv6ExtensionHeaderIdentifier, *bufferv2.View, error) {
// We ignore the number of bytes read because we know we will only ever read
// at max 1 bytes since rune has a length of 1. If we read 0 bytes, the Read
// would return io.EOF to indicate that io.Reader has reached the end of the
// payload.
nextHdrIdentifier, err := i.reader.ReadByte()
i.payload.TrimFront(1)
rdr := i.payload.AsBufferReader()
nextHdrIdentifier, err := rdr.ReadByte()
if err != nil {
return 0, nil, fmt.Errorf("error when reading the Next Header field for extension header with id = %d: %w", i.nextHdrIdentifier, err)
}
i.parseOffset++
var length uint8
length, err = i.reader.ReadByte()
i.payload.TrimFront(1)
length, err = rdr.ReadByte()
if err != nil {
if fragmentHdr {
@@ -668,19 +685,24 @@ func (i *IPv6PayloadIterator) nextHeaderData(fragmentHdr bool, bytes []byte) (IP
i.nextOffset += uint32((length + 1) * ipv6ExtHdrLenBytesPerUnit)
bytesLen := int(length)*ipv6ExtHdrLenBytesPerUnit + ipv6ExtHdrLenBytesExcluded
if bytes == nil {
bytes = make([]byte, bytesLen)
} else if n := len(bytes); n < bytesLen {
panic(fmt.Sprintf("bytes only has space for %d bytes but need space for %d bytes (length = %d) for extension header with id = %d", n, bytesLen, length, i.nextHdrIdentifier))
if fragmentHdr {
if n := len(bytes); n < bytesLen {
panic(fmt.Sprintf("bytes only has space for %d bytes but need space for %d bytes (length = %d) for extension header with id = %d", n, bytesLen, length, i.nextHdrIdentifier))
}
if n, err := io.ReadFull(&rdr, bytes); err != nil {
return 0, nil, fmt.Errorf("read %d out of %d extension header data bytes (length = %d) for header with id = %d: %w", n, bytesLen, length, i.nextHdrIdentifier, err)
}
return IPv6ExtensionHeaderIdentifier(nextHdrIdentifier), nil, nil
}
n, err := io.ReadFull(&i.reader, bytes)
i.payload.TrimFront(int64(n))
if err != nil {
v := bufferv2.NewView(bytesLen)
if n, err := io.CopyN(v, &rdr, int64(bytesLen)); err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
v.Release()
return 0, nil, fmt.Errorf("read %d out of %d extension header data bytes (length = %d) for header with id = %d: %w", n, bytesLen, length, i.nextHdrIdentifier, err)
}
return IPv6ExtensionHeaderIdentifier(nextHdrIdentifier), bytes, nil
return IPv6ExtensionHeaderIdentifier(nextHdrIdentifier), v, nil
}
// IPv6SerializableExtHdr provides serialization for IPv6 extension

Some files were not shown because too many files have changed in this diff Show More