fdbased: Vectorized write for packet; relax writev syscall filter.

Now it calls pkt.Data.ToView() when writing the packet. This may require
copying when the packet is large, which puts the worse case in an even worse
situation.

This sent out in a separate preparation change as it requires syscall filter
changes. This change will be followed by the change for the adoption of the new
PacketHeader API.

PiperOrigin-RevId: 321447003
This commit is contained in:
Ting-Yu Wang
2020-07-15 15:05:32 -07:00
committed by gVisor bot
parent 857d03f258
commit db653bb34b
11 changed files with 248 additions and 95 deletions
+18
View File
@@ -0,0 +1,18 @@
load("//tools:defs.bzl", "go_library", "go_test")
package(licenses = ["notice"])
go_library(
name = "iovec",
srcs = ["iovec.go"],
visibility = ["//:sandbox"],
deps = ["//pkg/abi/linux"],
)
go_test(
name = "iovec_test",
size = "small",
srcs = ["iovec_test.go"],
library = ":iovec",
deps = ["@org_golang_x_sys//unix:go_default_library"],
)
+75
View File
@@ -0,0 +1,75 @@
// Copyright 2020 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build linux
// Package iovec provides helpers to interact with vectorized I/O on host
// system.
package iovec
import (
"syscall"
"gvisor.dev/gvisor/pkg/abi/linux"
)
// MaxIovs is the maximum number of iovecs host platform can accept.
var MaxIovs = linux.UIO_MAXIOV
// Builder is a builder for slice of syscall.Iovec.
type Builder struct {
iovec []syscall.Iovec
storage [8]syscall.Iovec
// overflow tracks the last buffer when iovec length is at MaxIovs.
overflow []byte
}
// Add adds buf to b preparing to be written. Zero-length buf won't be added.
func (b *Builder) Add(buf []byte) {
if len(buf) == 0 {
return
}
if b.iovec == nil {
b.iovec = b.storage[:0]
}
if len(b.iovec) >= MaxIovs {
b.addByAppend(buf)
return
}
b.iovec = append(b.iovec, syscall.Iovec{
Base: &buf[0],
Len: uint64(len(buf)),
})
// Keep the last buf if iovec is at max capacity. We will need to append to it
// for later bufs.
if len(b.iovec) == MaxIovs {
n := len(buf)
b.overflow = buf[:n:n]
}
}
func (b *Builder) addByAppend(buf []byte) {
b.overflow = append(b.overflow, buf...)
b.iovec[len(b.iovec)-1] = syscall.Iovec{
Base: &b.overflow[0],
Len: uint64(len(b.overflow)),
}
}
// Build returns the final Iovec slice. The length of returned iovec will not
// excceed MaxIovs.
func (b *Builder) Build() []syscall.Iovec {
return b.iovec
}
+121
View File
@@ -0,0 +1,121 @@
// Copyright 2020 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build linux
package iovec
import (
"bytes"
"fmt"
"syscall"
"testing"
"unsafe"
"golang.org/x/sys/unix"
)
func TestBuilderEmpty(t *testing.T) {
var builder Builder
iovecs := builder.Build()
if got, want := len(iovecs), 0; got != want {
t.Errorf("len(iovecs) = %d, want %d", got, want)
}
}
func TestBuilderBuild(t *testing.T) {
a := []byte{1, 2}
b := []byte{3, 4, 5}
var builder Builder
builder.Add(a)
builder.Add(b)
builder.Add(nil) // Nil slice won't be added.
builder.Add([]byte{}) // Empty slice won't be added.
iovecs := builder.Build()
if got, want := len(iovecs), 2; got != want {
t.Fatalf("len(iovecs) = %d, want %d", got, want)
}
for i, data := range [][]byte{a, b} {
if got, want := *iovecs[i].Base, data[0]; got != want {
t.Fatalf("*iovecs[%d].Base = %d, want %d", i, got, want)
}
if got, want := iovecs[i].Len, uint64(len(data)); got != want {
t.Fatalf("iovecs[%d].Len = %d, want %d", i, got, want)
}
}
}
func TestBuilderBuildMaxIov(t *testing.T) {
for _, test := range []struct {
numIov int
}{
{
numIov: MaxIovs - 1,
},
{
numIov: MaxIovs,
},
{
numIov: MaxIovs + 1,
},
{
numIov: MaxIovs + 10,
},
} {
name := fmt.Sprintf("numIov=%v", test.numIov)
t.Run(name, func(t *testing.T) {
var data []byte
var builder Builder
for i := 0; i < test.numIov; i++ {
buf := []byte{byte(i)}
builder.Add(buf)
data = append(data, buf...)
}
iovec := builder.Build()
// Check the expected length of iovec.
wantNum := test.numIov
if wantNum > MaxIovs {
wantNum = MaxIovs
}
if got, want := len(iovec), wantNum; got != want {
t.Errorf("len(iovec) = %d, want %d", got, want)
}
// Test a real read-write.
var fds [2]int
if err := unix.Pipe(fds[:]); err != nil {
t.Fatalf("Pipe: %v", err)
}
defer syscall.Close(fds[0])
defer syscall.Close(fds[1])
wrote, _, e := syscall.RawSyscall(syscall.SYS_WRITEV, uintptr(fds[1]), uintptr(unsafe.Pointer(&iovec[0])), uintptr(len(iovec)))
if int(wrote) != len(data) || e != 0 {
t.Fatalf("writev: %v, %v; want %v, 0", wrote, e, len(data))
}
got := make([]byte, len(data))
if n, err := syscall.Read(fds[0], got); n != len(got) || err != nil {
t.Fatalf("read: %v, %v; want %v, nil", n, err, len(got))
}
if !bytes.Equal(got, data) {
t.Errorf("read: got data %v, want %v", got, data)
}
})
}
}
+1
View File
@@ -30,6 +30,7 @@ go_library(
"//pkg/context",
"//pkg/fd",
"//pkg/fdnotifier",
"//pkg/iovec",
"//pkg/log",
"//pkg/refs",
"//pkg/safemem",
+2 -5
View File
@@ -17,15 +17,12 @@ package host
import (
"syscall"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/iovec"
"gvisor.dev/gvisor/pkg/syserror"
)
// LINT.IfChange
// maxIovs is the maximum number of iovecs to pass to the host.
var maxIovs = linux.UIO_MAXIOV
// copyToMulti copies as many bytes from src to dst as possible.
func copyToMulti(dst [][]byte, src []byte) {
for _, d := range dst {
@@ -76,7 +73,7 @@ func buildIovec(bufs [][]byte, maxlen int64, truncate bool) (length int64, iovec
}
}
if iovsRequired > maxIovs {
if iovsRequired > iovec.MaxIovs {
// The kernel will reject our call if we pass this many iovs.
// Use a single intermediate buffer instead.
b := make([]byte, stopLen)
+1
View File
@@ -22,6 +22,7 @@ go_library(
"//pkg/context",
"//pkg/fdnotifier",
"//pkg/fspath",
"//pkg/iovec",
"//pkg/log",
"//pkg/refs",
"//pkg/safemem",
+2 -5
View File
@@ -17,13 +17,10 @@ package host
import (
"syscall"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/iovec"
"gvisor.dev/gvisor/pkg/syserror"
)
// maxIovs is the maximum number of iovecs to pass to the host.
var maxIovs = linux.UIO_MAXIOV
// copyToMulti copies as many bytes from src to dst as possible.
func copyToMulti(dst [][]byte, src []byte) {
for _, d := range dst {
@@ -74,7 +71,7 @@ func buildIovec(bufs [][]byte, maxlen int64, truncate bool) (length int64, iovec
}
}
if iovsRequired > maxIovs {
if iovsRequired > iovec.MaxIovs {
// The kernel will reject our call if we pass this many iovs.
// Use a single intermediate buffer instead.
b := make([]byte, stopLen)
+1
View File
@@ -15,6 +15,7 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//pkg/binary",
"//pkg/iovec",
"//pkg/sync",
"//pkg/tcpip",
"//pkg/tcpip/buffer",
+21 -48
View File
@@ -45,6 +45,7 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/binary"
"gvisor.dev/gvisor/pkg/iovec"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
@@ -406,6 +407,8 @@ func (e *endpoint) WritePacket(r *stack.Route, gso *stack.GSO, protocol tcpip.Ne
eth.Encode(ethHdr)
}
var builder iovec.Builder
fd := e.fds[pkt.Hash%uint32(len(e.fds))]
if e.Capabilities()&stack.CapabilityHardwareGSO != 0 {
vnetHdr := virtioNetHdr{}
@@ -430,29 +433,25 @@ func (e *endpoint) WritePacket(r *stack.Route, gso *stack.GSO, protocol tcpip.Ne
}
vnetHdrBuf := binary.Marshal(make([]byte, 0, virtioNetHdrSize), binary.LittleEndian, vnetHdr)
return rawfile.NonBlockingWrite3(fd, vnetHdrBuf, pkt.Header.View(), pkt.Data.ToView())
builder.Add(vnetHdrBuf)
}
if pkt.Data.Size() == 0 {
return rawfile.NonBlockingWrite(fd, pkt.Header.View())
}
if pkt.Header.UsedLength() == 0 {
return rawfile.NonBlockingWrite(fd, pkt.Data.ToView())
builder.Add(pkt.Header.View())
for _, v := range pkt.Data.Views() {
builder.Add(v)
}
return rawfile.NonBlockingWrite3(fd, pkt.Header.View(), pkt.Data.ToView(), nil)
return rawfile.NonBlockingWriteIovec(fd, builder.Build())
}
func (e *endpoint) sendBatch(batchFD int, batch []*stack.PacketBuffer) (int, *tcpip.Error) {
// Send a batch of packets through batchFD.
mmsgHdrs := make([]rawfile.MMsgHdr, 0, len(batch))
for _, pkt := range batch {
var ethHdrBuf []byte
iovLen := 0
var eth header.Ethernet
if e.hdrSize > 0 {
// Add ethernet header if needed.
ethHdrBuf = make([]byte, header.EthernetMinimumSize)
eth := header.Ethernet(ethHdrBuf)
eth = make(header.Ethernet, header.EthernetMinimumSize)
ethHdr := &header.EthernetFields{
DstAddr: pkt.EgressRoute.RemoteLinkAddress,
Type: pkt.NetworkProtocolNumber,
@@ -465,12 +464,11 @@ func (e *endpoint) sendBatch(batchFD int, batch []*stack.PacketBuffer) (int, *tc
ethHdr.SrcAddr = e.addr
}
eth.Encode(ethHdr)
iovLen++
}
vnetHdr := virtioNetHdr{}
var vnetHdrBuf []byte
if e.Capabilities()&stack.CapabilityHardwareGSO != 0 {
vnetHdr := virtioNetHdr{}
if pkt.GSOOptions != nil {
vnetHdr.hdrLen = uint16(pkt.Header.UsedLength())
if pkt.GSOOptions.NeedsCsum {
@@ -491,45 +489,20 @@ func (e *endpoint) sendBatch(batchFD int, batch []*stack.PacketBuffer) (int, *tc
}
}
vnetHdrBuf = binary.Marshal(make([]byte, 0, virtioNetHdrSize), binary.LittleEndian, vnetHdr)
iovLen++
}
iovecs := make([]syscall.Iovec, iovLen+1+len(pkt.Data.Views()))
var builder iovec.Builder
builder.Add(vnetHdrBuf)
builder.Add(eth)
builder.Add(pkt.Header.View())
for _, v := range pkt.Data.Views() {
builder.Add(v)
}
iovecs := builder.Build()
var mmsgHdr rawfile.MMsgHdr
mmsgHdr.Msg.Iov = &iovecs[0]
iovecIdx := 0
if vnetHdrBuf != nil {
v := &iovecs[iovecIdx]
v.Base = &vnetHdrBuf[0]
v.Len = uint64(len(vnetHdrBuf))
iovecIdx++
}
if ethHdrBuf != nil {
v := &iovecs[iovecIdx]
v.Base = &ethHdrBuf[0]
v.Len = uint64(len(ethHdrBuf))
iovecIdx++
}
pktSize := uint64(0)
// Encode L3 Header
v := &iovecs[iovecIdx]
hdr := &pkt.Header
hdrView := hdr.View()
v.Base = &hdrView[0]
v.Len = uint64(len(hdrView))
pktSize += v.Len
iovecIdx++
// Now encode the Transport Payload.
pktViews := pkt.Data.Views()
for i := range pktViews {
vec := &iovecs[iovecIdx]
iovecIdx++
vec.Base = &pktViews[i][0]
vec.Len = uint64(len(pktViews[i]))
pktSize += vec.Len
}
mmsgHdr.Msg.Iovlen = uint64(iovecIdx)
mmsgHdr.Msg.Iovlen = uint64(len(iovecs))
mmsgHdrs = append(mmsgHdrs, mmsgHdr)
}
+4 -28
View File
@@ -66,38 +66,14 @@ func NonBlockingWrite(fd int, buf []byte) *tcpip.Error {
return nil
}
// NonBlockingWrite3 writes up to three byte slices to a file descriptor in a
// single syscall. It fails if partial data is written.
func NonBlockingWrite3(fd int, b1, b2, b3 []byte) *tcpip.Error {
// If there is no second and third buffer, issue a regular write.
if len(b2) == 0 && len(b3) == 0 {
return NonBlockingWrite(fd, b1)
}
// Build the iovec that represents them and issue a writev syscall.
iovec := [3]syscall.Iovec{
{
Base: &b1[0],
Len: uint64(len(b1)),
},
{
Base: &b2[0],
Len: uint64(len(b2)),
},
}
iovecLen := uintptr(2)
if len(b3) > 0 {
iovecLen++
iovec[2].Base = &b3[0]
iovec[2].Len = uint64(len(b3))
}
// NonBlockingWriteIovec writes iovec to a file descriptor in a single syscall.
// It fails if partial data is written.
func NonBlockingWriteIovec(fd int, iovec []syscall.Iovec) *tcpip.Error {
iovecLen := uintptr(len(iovec))
_, _, e := syscall.RawSyscall(syscall.SYS_WRITEV, uintptr(fd), uintptr(unsafe.Pointer(&iovec[0])), iovecLen)
if e != 0 {
return TranslateErrno(e)
}
return nil
}
+2 -9
View File
@@ -310,19 +310,12 @@ var allowedSyscalls = seccomp.SyscallRules{
},
},
syscall.SYS_WRITE: {},
// The only user in rawfile.NonBlockingWrite3 always passes iovcnt with
// values 2 or 3. Three iovec-s are passed, when the PACKET_VNET_HDR
// option is enabled for a packet socket.
// For rawfile.NonBlockingWriteIovec.
syscall.SYS_WRITEV: []seccomp.Rule{
{
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.AllowValue(2),
},
{
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.AllowValue(3),
seccomp.GreaterThan(0),
},
},
}