mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
@@ -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"],
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ go_library(
|
||||
"//pkg/context",
|
||||
"//pkg/fd",
|
||||
"//pkg/fdnotifier",
|
||||
"//pkg/iovec",
|
||||
"//pkg/log",
|
||||
"//pkg/refs",
|
||||
"//pkg/safemem",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -22,6 +22,7 @@ go_library(
|
||||
"//pkg/context",
|
||||
"//pkg/fdnotifier",
|
||||
"//pkg/fspath",
|
||||
"//pkg/iovec",
|
||||
"//pkg/log",
|
||||
"//pkg/refs",
|
||||
"//pkg/safemem",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -15,6 +15,7 @@ go_library(
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/binary",
|
||||
"//pkg/iovec",
|
||||
"//pkg/sync",
|
||||
"//pkg/tcpip",
|
||||
"//pkg/tcpip/buffer",
|
||||
|
||||
@@ -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 = ðHdrBuf[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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user