Improve darwin tun performance

This commit is contained in:
世界
2025-07-17 17:33:41 +08:00
parent 7812930a48
commit aa1fd4d994
16 changed files with 192 additions and 115 deletions

View File

@@ -168,6 +168,7 @@ type endpoint struct {
mtu uint32
batchSize int
sendMsgX bool
}
// Options specify the details about the fd-based endpoint to be created.
@@ -223,6 +224,9 @@ type Options struct {
// ProcessorsPerChannel is the number of goroutines used to handle packets
// from each FD.
ProcessorsPerChannel int
MultiPendingPackets bool
SendMsgX bool
}
// New creates a new fd-based endpoint.
@@ -261,6 +265,12 @@ func New(opts *Options) (stack.LinkEndpoint, error) {
if opts.MaxSyscallHeaderBytes < 0 {
return nil, fmt.Errorf("opts.MaxSyscallHeaderBytes is negative")
}
var batchSize int
if opts.MultiPendingPackets {
batchSize = int((512*1024)/(opts.MTU)) + 1
} else {
batchSize = 1
}
e := &endpoint{
mtu: opts.MTU,
@@ -271,7 +281,8 @@ func New(opts *Options) (stack.LinkEndpoint, error) {
packetDispatchMode: opts.PacketDispatchMode,
maxSyscallHeaderBytes: uintptr(opts.MaxSyscallHeaderBytes),
writevMaxIovs: rawfile.MaxIovs,
batchSize: int((512*1024)/(opts.MTU)) + 1,
batchSize: batchSize,
sendMsgX: opts.SendMsgX,
}
if e.maxSyscallHeaderBytes != 0 {
if max := int(e.maxSyscallHeaderBytes / rawfile.SizeofIovec); max < e.writevMaxIovs {
@@ -478,7 +489,7 @@ func (e *endpoint) writePacket(pkt *stack.PacketBuffer) tcpip.Error {
func (e *endpoint) sendBatch(batchFDInfo fdInfo, pkts []*stack.PacketBuffer) (int, tcpip.Error) {
// Degrade to writePacket if underlying fd is not a socket.
if !batchFDInfo.isSocket {
if !batchFDInfo.isSocket || !e.sendMsgX {
var written int
var err tcpip.Error
for written < len(pkts) {

View File

@@ -116,7 +116,7 @@ func newRecvMMsgDispatcher(fd int, e *endpoint, opts *Options) (linkDispatcher,
return nil, err
}
var batchSize int
if opts.MTU < 49152 {
if opts.MultiPendingPackets {
batchSize = int((512*1024)/(opts.MTU)) + 1
} else {
batchSize = 1

View File

@@ -40,6 +40,7 @@ type GVisor struct {
type GVisorTun interface {
Tun
WritePacket(pkt *stack.PacketBuffer) (int, error)
NewEndpoint() (stack.LinkEndpoint, stack.NICOptions, error)
}

View File

@@ -8,8 +8,6 @@ import (
"github.com/sagernet/gvisor/pkg/tcpip"
"github.com/sagernet/gvisor/pkg/tcpip/header"
"github.com/sagernet/gvisor/pkg/tcpip/stack"
"github.com/sagernet/sing/common/bufio"
N "github.com/sagernet/sing/common/network"
)
var _ stack.LinkEndpoint = (*LinkEndpointFilter)(nil)
@@ -17,7 +15,7 @@ var _ stack.LinkEndpoint = (*LinkEndpointFilter)(nil)
type LinkEndpointFilter struct {
stack.LinkEndpoint
BroadcastAddress netip.Addr
Writer N.VectorisedWriter
Writer GVisorTun
}
func (w *LinkEndpointFilter) Attach(dispatcher stack.NetworkDispatcher) {
@@ -29,7 +27,7 @@ var _ stack.NetworkDispatcher = (*networkDispatcherFilter)(nil)
type networkDispatcherFilter struct {
stack.NetworkDispatcher
broadcastAddress netip.Addr
writer N.VectorisedWriter
writer GVisorTun
}
func (w *networkDispatcherFilter) DeliverNetworkPacket(protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
@@ -49,7 +47,7 @@ func (w *networkDispatcherFilter) DeliverNetworkPacket(protocol tcpip.NetworkPro
}
destination := AddrFromAddress(network.DestinationAddress())
if destination == w.broadcastAddress || !destination.IsGlobalUnicast() {
_, _ = bufio.WriteVectorised(w.writer, pkt.AsSlices())
w.writer.WritePacket(pkt)
return
}
w.NetworkDispatcher.DeliverNetworkPacket(protocol, pkt)

View File

@@ -13,7 +13,6 @@ import (
"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp"
"github.com/sagernet/sing-tun/internal/gtcpip/checksum"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
@@ -56,7 +55,7 @@ func (f *TCPForwarder) HandlePacket(id stack.TransportEndpointID, pkt *stack.Pac
tcpHdr.SetChecksum(^checksum.Checksum(tcpHdr.Payload(), tcpHdr.CalculateChecksum(
header.PseudoHeaderChecksum(header.TCPProtocolNumber, ipHdr.SourceAddress(), ipHdr.DestinationAddress(), ipHdr.PayloadLength()),
)))
bufio.WriteVectorised(f.tun, pkt.AsSlices())
f.tun.WritePacket(pkt)
return true
}
}
@@ -70,7 +69,7 @@ func (f *TCPForwarder) HandlePacket(id stack.TransportEndpointID, pkt *stack.Pac
tcpHdr.SetChecksum(^checksum.Checksum(tcpHdr.Payload(), tcpHdr.CalculateChecksum(
header.PseudoHeaderChecksum(header.TCPProtocolNumber, ipHdr.SourceAddress(), ipHdr.DestinationAddress(), ipHdr.PayloadLength()),
)))
bufio.WriteVectorised(f.tun, pkt.AsSlices())
f.tun.WritePacket(pkt)
return true
}
}

View File

@@ -11,12 +11,12 @@ import (
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
"github.com/sagernet/sing-tun/internal/gtcpip/header"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
)
type Mixed struct {
*System
tun GVisorTun
stack *stack.Stack
endpoint *channel.Endpoint
}
@@ -30,6 +30,7 @@ func NewMixed(
}
return &Mixed{
System: system.(*System),
tun: system.(*System).tun.(GVisorTun),
}, nil
}
@@ -77,7 +78,7 @@ func (m *Mixed) tunLoop() {
return
}
}
if darwinTUN, isDarwinTUN := m.tun.(DarwinTUN); isDarwinTUN && m.mtu < 49152 {
if darwinTUN, isDarwinTUN := m.tun.(DarwinTUN); isDarwinTUN && m.multiPendingPackets {
m.batchLoopDarwin(darwinTUN)
return
}
@@ -265,11 +266,11 @@ func (m *Mixed) processIPv6(ipHdr header.IPv6) (writeBack bool, err error) {
func (m *Mixed) packetLoop() {
for {
packet := m.endpoint.ReadContext(m.ctx)
if packet == nil {
pkt := m.endpoint.ReadContext(m.ctx)
if pkt == nil {
break
}
bufio.WriteVectorised(m.tun, packet.AsSlices())
packet.DecRef()
m.tun.WritePacket(pkt)
pkt.DecRef()
}
}

View File

@@ -49,6 +49,7 @@ type System struct {
interfaceFinder control.InterfaceFinder
frontHeadroom int
txChecksumOffload bool
multiPendingPackets bool
}
type Session struct {
@@ -74,6 +75,7 @@ func NewSystem(options StackOptions) (Stack, error) {
broadcastAddr: BroadcastAddr(options.TunOptions.Inet4Address),
bindInterface: options.ForwarderBindInterface,
interfaceFinder: options.InterfaceFinder,
multiPendingPackets: options.TunOptions.EXP_MultiPendingPackets,
}
if len(options.TunOptions.Inet4Address) > 0 {
if !HasNextAddress(options.TunOptions.Inet4Address[0], 1) {
@@ -174,7 +176,7 @@ func (s *System) tunLoop() {
return
}
}
if darwinTUN, isDarwinTUN := s.tun.(DarwinTUN); isDarwinTUN && s.mtu < 49152 {
if darwinTUN, isDarwinTUN := s.tun.(DarwinTUN); isDarwinTUN && s.multiPendingPackets {
s.batchLoopDarwin(darwinTUN)
return
}
@@ -320,6 +322,13 @@ func (s *System) acceptLoop(listener net.Listener) {
if err != nil {
return
}
err = acceptConn(conn)
if err != nil {
s.logger.Error("set buffer for conn: ", err)
_ = conn.Close()
listener.Close()
return
}
connPort := M.SocksaddrFromNet(conn.RemoteAddr()).Port
session := s.tcpNat.LookupBack(connPort)
if session == nil {

26
stack_system_unix.go Normal file
View File

@@ -0,0 +1,26 @@
//go:build !windows
package tun
import (
"net"
"github.com/sagernet/sing/common/control"
"golang.org/x/sys/unix"
)
func acceptConn(conn net.Conn) error {
return control.Conn(conn.(*net.TCPConn), func(fd uintptr) error {
const bufferSize = 1024 * 1024
oErr := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RCVBUF, bufferSize)
if oErr != nil {
return oErr
}
oErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_SNDBUF, bufferSize)
if oErr != nil {
return oErr
}
return nil
})
}

View File

@@ -2,6 +2,7 @@ package tun
import (
"errors"
"net"
"os"
"path/filepath"
@@ -31,3 +32,7 @@ func fixWindowsFirewall() error {
func retryableListenError(err error) bool {
return errors.Is(err, windows.WSAEADDRNOTAVAIL)
}
func acceptConn(conn net.Conn) error {
return nil
}

8
tun.go
View File

@@ -25,7 +25,6 @@ type Handler interface {
type Tun interface {
io.ReadWriter
N.VectorisedWriter
Name() (string, error)
Start() error
Close() error
@@ -97,6 +96,13 @@ type Options struct {
// For library usages.
EXP_DisableDNSHijack bool
// For gvisor stack, it should be enabled when MTU is less than 32768; otherwise it should be less than or equal to 8192.
// The above condition is just an estimate and not exact, calculated on M4 pro.
EXP_MultiPendingPackets bool
// Will cause the darwin network to die, do not use.
EXP_SendMsgX bool
}
func (o *Options) Inet4GatewayAddr() netip.Addr {

View File

@@ -14,9 +14,7 @@ import (
"github.com/sagernet/sing-tun/internal/stopfd_darwin"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/shell"
"golang.org/x/net/route"
@@ -33,15 +31,16 @@ type NativeTun struct {
batchSize int
iovecs []iovecBuffer
iovecsOutput []iovecBuffer
iovecsOutputDefault []unix.Iovec
msgHdrs []rawfile.MsgHdrX
msgHdrsOutput []rawfile.MsgHdrX
buffers []*buf.Buffer
stopFd stopfd.StopFD
tunWriter N.VectorisedWriter
options Options
inet4Address [4]byte
inet6Address [16]byte
routeSet bool
writeMsgX bool
}
type iovecBuffer struct {
@@ -111,14 +110,14 @@ func New(options Options) (Tun, error) {
unix.Close(tunFd)
return nil, err
}
err = configure(tunFd, options.MTU, batchSize)
err = configure(tunFd, options.EXP_MultiPendingPackets, batchSize)
if err != nil {
unix.Close(tunFd)
return nil, err
}
} else {
tunFd = options.FileDescriptor
err := configure(tunFd, options.MTU, batchSize)
err := configure(tunFd, options.EXP_MultiPendingPackets, batchSize)
if err != nil {
return nil, err
}
@@ -133,6 +132,7 @@ func New(options Options) (Tun, error) {
msgHdrs: make([]rawfile.MsgHdrX, batchSize),
msgHdrsOutput: make([]rawfile.MsgHdrX, batchSize),
stopFd: common.Must1(stopfd.New()),
writeMsgX: options.EXP_SendMsgX,
}
for i := 0; i < batchSize; i++ {
nativeTun.iovecs[i] = newIovecBuffer(int(options.MTU))
@@ -144,11 +144,6 @@ func New(options Options) (Tun, error) {
if len(options.Inet6Address) > 0 {
nativeTun.inet6Address = options.Inet6Address[0].Addr().As16()
}
var ok bool
nativeTun.tunWriter, ok = bufio.CreateVectorisedWriter(nativeTun.tunFile)
if !ok {
panic("create vectorised writer")
}
return nativeTun, nil
}
@@ -182,17 +177,6 @@ func init() {
packetHeaderVec6.SetLen(4)
}
func (t *NativeTun) WriteVectorised(buffers []*buf.Buffer) error {
var packetHeader []byte
switch header.IPVersion(buffers[0].Bytes()) {
case header.IPv4Version:
packetHeader = packetHeader4[:]
case header.IPv6Version:
packetHeader = packetHeader6[:]
}
return t.tunWriter.WriteVectorised(append([]*buf.Buffer{buf.As(packetHeader)}, buffers...))
}
const utunControlName = "com.apple.net.utun_control"
const (
@@ -332,12 +316,12 @@ func create(tunFd int, ifIndex int, name string, options Options) error {
return nil
}
func configure(tunFd int, tunMTU uint32, batchSize int) error {
func configure(tunFd int, multiPendingPackets bool, batchSize int) error {
err := unix.SetNonblock(tunFd, true)
if err != nil {
return os.NewSyscallError("SetNonblock", err)
}
if tunMTU < 49152 {
if multiPendingPackets {
const UTUN_OPT_MAX_PENDING_PACKETS = 16
err = unix.SetsockoptInt(tunFd, 2, UTUN_OPT_MAX_PENDING_PACKETS, batchSize)
if err != nil {
@@ -347,10 +331,6 @@ func configure(tunFd int, tunMTU uint32, batchSize int) error {
return nil
}
func (t *NativeTun) BatchSize() int {
return t.batchSize
}
func (t *NativeTun) BatchRead() ([]*buf.Buffer, error) {
for i := 0; i < t.batchSize; i++ {
iovecs := t.iovecs[i].nextIovecs()
@@ -384,6 +364,17 @@ func (t *NativeTun) BatchRead() ([]*buf.Buffer, error) {
}
func (t *NativeTun) BatchWrite(buffers []*buf.Buffer) error {
if !t.writeMsgX {
for i, buffer := range buffers {
t.iovecsOutput[i].nextIovecsOutput(buffer)
}
for i := range buffers {
errno := rawfile.NonBlockingWriteIovec(t.tunFd, t.iovecsOutput[i].iovecs)
if errno != 0 {
return errno
}
}
} else {
for i, buffer := range buffers {
iovecs := t.iovecsOutput[i].nextIovecsOutput(buffer)
t.msgHdrsOutput[i] = rawfile.MsgHdrX{}
@@ -394,6 +385,7 @@ func (t *NativeTun) BatchWrite(buffers []*buf.Buffer) error {
if errno != 0 {
return errno
}
}
return nil
}

View File

@@ -6,16 +6,43 @@ import (
"github.com/sagernet/gvisor/pkg/tcpip/link/qdisc/fifo"
"github.com/sagernet/gvisor/pkg/tcpip/stack"
"github.com/sagernet/sing-tun/internal/fdbased_darwin"
"github.com/sagernet/sing-tun/internal/rawfile_darwin"
"golang.org/x/sys/unix"
)
var _ GVisorTun = (*NativeTun)(nil)
func (t *NativeTun) WritePacket(pkt *stack.PacketBuffer) (int, error) {
iovecs := t.iovecsOutputDefault
var dataLen int
for _, packetSlice := range pkt.AsSlices() {
dataLen += len(packetSlice)
iovec := unix.Iovec{
Base: &packetSlice[0],
}
iovec.SetLen(len(packetSlice))
iovecs = append(iovecs, iovec)
}
if cap(iovecs) > cap(t.iovecsOutputDefault) {
t.iovecsOutputDefault = iovecs[:0]
}
errno := rawfile.NonBlockingWriteIovec(t.tunFd, iovecs)
if errno == 0 {
return dataLen, nil
} else {
return 0, errno
}
}
func (t *NativeTun) NewEndpoint() (stack.LinkEndpoint, stack.NICOptions, error) {
ep, err := fdbased.New(&fdbased.Options{
FDs: []int{t.tunFd},
MTU: t.options.MTU,
RXChecksumOffload: true,
PacketDispatchMode: fdbased.RecvMMsg,
MultiPendingPackets: t.options.EXP_MultiPendingPackets,
SendMsgX: t.options.EXP_SendMsgX,
})
if err != nil {
return nil, stack.NICOptions{}, err

View File

@@ -18,10 +18,8 @@ import (
"github.com/sagernet/sing-tun/internal/gtcpip/header"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/shell"
"github.com/sagernet/sing/common/x/list"
@@ -34,7 +32,7 @@ var _ LinuxTUN = (*NativeTun)(nil)
type NativeTun struct {
tunFd int
tunFile *os.File
tunWriter N.VectorisedWriter
iovecsOutputDefault []unix.Iovec
interfaceCallback *list.Element[DefaultInterfaceUpdateCallback]
options Options
ruleIndex6 []int
@@ -77,11 +75,6 @@ func New(options Options) (Tun, error) {
options: options,
}
}
var ok bool
nativeTun.tunWriter, ok = bufio.CreateVectorisedWriter(nativeTun.tunFile)
if !ok {
panic("create vectorised writer")
}
return nativeTun, nil
}
@@ -402,20 +395,6 @@ func (t *NativeTun) Write(p []byte) (n int, err error) {
return t.tunFile.Write(p)
}
func (t *NativeTun) WriteVectorised(buffers []*buf.Buffer) error {
if t.vnetHdr {
n := buf.LenMulti(buffers)
buffer := buf.NewSize(virtioNetHdrLen + n)
buffer.Truncate(virtioNetHdrLen)
buf.CopyMulti(buffer.Extend(n), buffers)
_, err := t.tunFile.Write(buffer.Bytes())
buffer.Release()
return err
} else {
return t.tunWriter.WriteVectorised(buffers)
}
}
func (t *NativeTun) FrontHeadroom() int {
if t.vnetHdr {
return virtioNetHdrLen

View File

@@ -3,8 +3,11 @@
package tun
import (
"github.com/sagernet/gvisor/pkg/rawfile"
"github.com/sagernet/gvisor/pkg/tcpip/link/fdbased"
"github.com/sagernet/gvisor/pkg/tcpip/stack"
"golang.org/x/sys/unix"
)
func init() {
@@ -13,6 +16,28 @@ func init() {
var _ GVisorTun = (*NativeTun)(nil)
func (t *NativeTun) WritePacket(pkt *stack.PacketBuffer) (int, error) {
iovecs := t.iovecsOutputDefault
var dataLen int
for _, packetSlice := range pkt.AsSlices() {
dataLen += len(packetSlice)
iovec := unix.Iovec{
Base: &packetSlice[0],
}
iovec.SetLen(len(packetSlice))
iovecs = append(iovecs, iovec)
}
if cap(iovecs) > cap(t.iovecsOutputDefault) {
t.iovecsOutputDefault = iovecs[:0]
}
errno := rawfile.NonBlockingWriteIovec(t.tunFd, iovecs)
if errno == 0 {
return dataLen, nil
} else {
return 0, errno
}
}
func (t *NativeTun) NewEndpoint() (stack.LinkEndpoint, stack.NICOptions, error) {
if t.vnetHdr {
ep, err := fdbased.New(&fdbased.Options{

View File

@@ -17,7 +17,6 @@ import (
"github.com/sagernet/sing-tun/internal/wintun"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/windnsapi"
@@ -517,11 +516,6 @@ func (t *NativeTun) write(packetElementList [][]byte) (n int, err error) {
return 0, fmt.Errorf("write failed: %w", err)
}
func (t *NativeTun) WriteVectorised(buffers []*buf.Buffer) error {
defer buf.ReleaseMulti(buffers)
return common.Error(t.write(buf.ToSliceMulti(buffers)))
}
func (t *NativeTun) Close() error {
var err error
t.closeOnce.Do(func() {

View File

@@ -11,6 +11,10 @@ import (
var _ GVisorTun = (*NativeTun)(nil)
func (t *NativeTun) WritePacket(pkt *stack.PacketBuffer) (int, error) {
return t.write(pkt.AsSlices())
}
func (t *NativeTun) NewEndpoint() (stack.LinkEndpoint, stack.NICOptions, error) {
return &WintunEndpoint{tun: t}, stack.NICOptions{}, nil
}