diff --git a/common/buf/buffer.go b/common/buf/buffer.go index 9c57b71..73116e5 100644 --- a/common/buf/buffer.go +++ b/common/buf/buffer.go @@ -321,6 +321,12 @@ func (b *Buffer) Release() { *b = Buffer{closed: true} } +func ReleaseMulti(buffers []*Buffer) { + for _, buffer := range buffers { + buffer.Release() + } +} + func (b *Buffer) Cut(start int, end int) *Buffer { b.start += start b.end = len(b.data) - end diff --git a/common/bufio/io.go b/common/bufio/io.go index 0dea32b..36cc295 100644 --- a/common/bufio/io.go +++ b/common/bufio/io.go @@ -38,3 +38,17 @@ func WriteTo(writer N.PacketWriter, buffer *buf.Buffer, addr net.Addr) (n int, e } return } + +func WriteVectorised(writer N.VectorisedWriter, data [][]byte) (n int, err error) { + var dataLen int + buffers := make([]*buf.Buffer, 0, len(data)) + for _, p := range data { + dataLen += len(p) + buffers = append(buffers, buf.As(p)) + } + err = writer.WriteVectorised(buffers) + if err == nil { + n = dataLen + } + return +} diff --git a/common/bufio/vectorised.go b/common/bufio/vectorised.go new file mode 100644 index 0000000..7b24af4 --- /dev/null +++ b/common/bufio/vectorised.go @@ -0,0 +1,109 @@ +package bufio + +import ( + "io" + "net" + "syscall" + + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func CreateVectorisedWriter(writer any) (N.VectorisedWriter, bool) { + switch w := writer.(type) { + case N.VectorisedWriter: + return w, true + case *net.TCPConn: + return &NetVectorisedWriterWrapper{w}, true + case *net.UDPConn: + return &NetVectorisedWriterWrapper{w}, true + case *net.IPConn: + return &NetVectorisedWriterWrapper{w}, true + case *net.UnixConn: + return &NetVectorisedWriterWrapper{w}, true + case syscall.Conn: + rawConn, err := w.SyscallConn() + if err != nil { + return &SyscallVectorisedWriter{writer, rawConn}, true + } + case syscall.RawConn: + return &SyscallVectorisedWriter{writer, w}, true + } + return nil, false +} + +func CreateVectorisedPacketWriter(writer any) (N.VectorisedPacketWriter, bool) { + switch w := writer.(type) { + case N.VectorisedPacketWriter: + return w, true + case syscall.Conn: + rawConn, err := w.SyscallConn() + if err != nil { + return &SyscallVectorisedPacketWriter{writer, rawConn}, true + } + case syscall.RawConn: + return &SyscallVectorisedPacketWriter{writer, w}, true + } + return nil, false +} + +var _ N.VectorisedWriter = (*NetVectorisedWriterWrapper)(nil) + +type NetVectorisedWriterWrapper struct { + upstream io.Writer +} + +func (w *NetVectorisedWriterWrapper) WriteVectorised(buffers []*buf.Buffer) error { + defer buf.ReleaseMulti(buffers) + netBuffers := make(net.Buffers, 0, len(buffers)) + for _, buffer := range buffers { + netBuffers = append(netBuffers, buffer.Bytes()) + } + return common.Error(netBuffers.WriteTo(w.upstream)) +} + +func (w *NetVectorisedWriterWrapper) Upstream() any { + return w.upstream +} + +func (w *NetVectorisedWriterWrapper) WriterReplaceable() bool { + return true +} + +var _ N.VectorisedWriter = (*SyscallVectorisedWriter)(nil) + +type SyscallVectorisedWriter struct { + upstream any + rawConn syscall.RawConn +} + +func (w *SyscallVectorisedWriter) Upstream() any { + return w.upstream +} + +func (w *SyscallVectorisedWriter) WriterReplaceable() bool { + return true +} + +var _ N.VectorisedPacketWriter = (*SyscallVectorisedPacketWriter)(nil) + +type SyscallVectorisedPacketWriter struct { + upstream any + rawConn syscall.RawConn +} + +func (w *SyscallVectorisedPacketWriter) Upstream() any { + return w.upstream +} + +var _ N.VectorisedPacketWriter = (*UnbindVectorisedPacketWriter)(nil) + +type UnbindVectorisedPacketWriter struct { + N.VectorisedWriter +} + +func (w *UnbindVectorisedPacketWriter) WriteVectorisedPacket(buffers []*buf.Buffer, _ M.Socksaddr) error { + return w.WriteVectorised(buffers) +} diff --git a/common/bufio/vectorised_unix.go b/common/bufio/vectorised_unix.go new file mode 100644 index 0000000..b2ba806 --- /dev/null +++ b/common/bufio/vectorised_unix.go @@ -0,0 +1,57 @@ +//go:build !windows + +package bufio + +import ( + "unsafe" + + "github.com/sagernet/sing/common/buf" + M "github.com/sagernet/sing/common/metadata" + + "golang.org/x/sys/unix" +) + +func (w *SyscallVectorisedWriter) WriteVectorised(buffers []*buf.Buffer) error { + defer buf.ReleaseMulti(buffers) + iovecList := make([]unix.Iovec, 0, len(buffers)) + for _, buffer := range buffers { + var iovec unix.Iovec + iovec.Base = &buffer.Bytes()[0] + iovec.SetLen(buffer.Len()) + iovecList = append(iovecList, iovec) + } + var innerErr unix.Errno + err := w.rawConn.Write(func(fd uintptr) (done bool) { + _, _, innerErr = unix.Syscall(unix.SYS_WRITEV, fd, uintptr(unsafe.Pointer(&iovecList[0])), uintptr(len(iovecList))) + return innerErr != unix.EAGAIN && innerErr != unix.EWOULDBLOCK + }) + if innerErr != 0 { + err = innerErr + } + return err +} + +func (w *SyscallVectorisedPacketWriter) WriteVectorisedPacket(buffers []*buf.Buffer, destination M.Socksaddr) error { + iovecList := make([]unix.Iovec, 0, len(buffers)) + for _, buffer := range buffers { + var iovec unix.Iovec + iovec.Base = &buffer.Bytes()[0] + iovec.SetLen(buffer.Len()) + iovecList = append(iovecList, iovec) + } + name, nameLen := destination.Sockaddr() + var msgHdr unix.Msghdr + msgHdr.Name = (*byte)(name) + msgHdr.Namelen = nameLen + msgHdr.Iov = &iovecList[0] + msgHdr.SetIovlen(len(iovecList)) + var innerErr unix.Errno + err := w.rawConn.Write(func(fd uintptr) (done bool) { + _, _, innerErr = unix.Syscall(unix.SYS_SENDMSG, fd, uintptr(unsafe.Pointer(&msgHdr)), 0) + return innerErr != unix.EAGAIN && innerErr != unix.EWOULDBLOCK + }) + if innerErr != 0 { + err = innerErr + } + return err +} diff --git a/common/bufio/vectorised_windows.go b/common/bufio/vectorised_windows.go new file mode 100644 index 0000000..1a31c55 --- /dev/null +++ b/common/bufio/vectorised_windows.go @@ -0,0 +1,50 @@ +package bufio + +import ( + "github.com/sagernet/sing/common/buf" + M "github.com/sagernet/sing/common/metadata" + + "golang.org/x/sys/windows" +) + +func (w *SyscallVectorisedWriter) WriteVectorised(buffers []*buf.Buffer) error { + defer buf.ReleaseMulti(buffers) + iovecList := make([]*windows.WSABuf, len(buffers)) + for i, buffer := range buffers { + iovecList[i] = &windows.WSABuf{ + Len: uint32(buffer.Len()), + Buf: &buffer.Bytes()[0], + } + } + var n uint32 + var innerErr error + err := w.rawConn.Write(func(fd uintptr) (done bool) { + innerErr = windows.WSASend(windows.Handle(fd), iovecList[0], uint32(len(iovecList)), &n, 0, nil, nil) + return innerErr != windows.WSAEWOULDBLOCK + }) + if innerErr != nil { + err = innerErr + } + return err +} + +func (w *SyscallVectorisedPacketWriter) WriteVectorisedPacket(buffers []*buf.Buffer, destination M.Socksaddr) error { + defer buf.ReleaseMulti(buffers) + iovecList := make([]*windows.WSABuf, len(buffers)) + for i, buffer := range buffers { + iovecList[i] = &windows.WSABuf{ + Len: uint32(buffer.Len()), + Buf: &buffer.Bytes()[0], + } + } + var n uint32 + var innerErr error + err := w.rawConn.Write(func(fd uintptr) (done bool) { + innerErr = windows.WSASendto(windows.Handle(fd), iovecList[0], uint32(len(iovecList)), &n, 0, destination.Sockaddr(), nil, nil) + return innerErr != windows.WSAEWOULDBLOCK + }) + if innerErr != nil { + err = innerErr + } + return err +} diff --git a/common/metadata/addr_posix.go b/common/metadata/addr_posix.go new file mode 100644 index 0000000..a0e9a3f --- /dev/null +++ b/common/metadata/addr_posix.go @@ -0,0 +1,31 @@ +//go:build !windows + +package metadata + +import ( + "syscall" + "unsafe" +) + +func (ap Socksaddr) Sockaddr() (name unsafe.Pointer, nameLen uint32) { + if ap.IsFqdn() { + panic("bad sockaddr") + } else if ap.IsIPv4() { + rsa4 := syscall.RawSockaddrInet4{ + Family: syscall.AF_INET, + Port: ap.Port, + Addr: ap.Addr.As4(), + } + name = unsafe.Pointer(&rsa4) + nameLen = syscall.SizeofSockaddrInet4 + } else { + rsa6 := syscall.RawSockaddrInet6{ + Family: syscall.AF_INET6, + Port: ap.Port, + Addr: ap.Addr.As16(), + } + name = unsafe.Pointer(&rsa6) + nameLen = syscall.SizeofSockaddrInet6 + } + return +} diff --git a/common/metadata/addr_windows.go b/common/metadata/addr_windows.go new file mode 100644 index 0000000..1aa1eef --- /dev/null +++ b/common/metadata/addr_windows.go @@ -0,0 +1,21 @@ +package metadata + +import ( + "golang.org/x/sys/windows" +) + +func (ap Socksaddr) Sockaddr() windows.Sockaddr { + if ap.IsFqdn() { + panic("bad sockaddr") + } else if ap.IsIPv4() { + return &windows.SockaddrInet4{ + Port: int(ap.Port), + Addr: ap.Addr.As4(), + } + } else { + return &windows.SockaddrInet6{ + Port: int(ap.Port), + Addr: ap.Addr.As16(), + } + } +} diff --git a/common/network/vectorised.go b/common/network/vectorised.go new file mode 100644 index 0000000..d6a2354 --- /dev/null +++ b/common/network/vectorised.go @@ -0,0 +1,14 @@ +package network + +import ( + "github.com/sagernet/sing/common/buf" + M "github.com/sagernet/sing/common/metadata" +) + +type VectorisedWriter interface { + WriteVectorised(buffers []*buf.Buffer) error +} + +type VectorisedPacketWriter interface { + WriteVectorisedPacket(buffers []*buf.Buffer, destination M.Socksaddr) error +} diff --git a/common/rw/writev_posix.go b/common/rw/writev_posix.go index 1c471a4..9758b98 100644 --- a/common/rw/writev_posix.go +++ b/common/rw/writev_posix.go @@ -7,6 +7,7 @@ import ( "unsafe" ) +// Deprecated: use vectorised writer func WriteV(fd uintptr, data [][]byte) (int, error) { iovecs := make([]syscall.Iovec, len(data)) for i := range iovecs { diff --git a/common/rw/writev_windows.go b/common/rw/writev_windows.go index 7dc223e..ae1e5ad 100644 --- a/common/rw/writev_windows.go +++ b/common/rw/writev_windows.go @@ -4,6 +4,7 @@ import ( "syscall" ) +// Deprecated: use vectorised writer func WriteV(fd uintptr, data [][]byte) (int, error) { var n uint32 buffers := make([]*syscall.WSABuf, len(data)) diff --git a/protocol/socks/packet.go b/protocol/socks/packet.go index 7af9e39..bc17b0f 100644 --- a/protocol/socks/packet.go +++ b/protocol/socks/packet.go @@ -17,7 +17,7 @@ import ( // +----+------+------+----------+----------+----------+ type AssociatePacketConn struct { - N.PacketConn + N.NetPacketConn addr net.Addr remoteAddr M.Socksaddr underlying net.Conn @@ -25,17 +25,17 @@ type AssociatePacketConn struct { func NewAssociatePacketConn(conn net.PacketConn, remoteAddr M.Socksaddr, underlying net.Conn) *AssociatePacketConn { return &AssociatePacketConn{ - PacketConn: bufio.NewPacketConn(conn), - remoteAddr: remoteAddr, - underlying: underlying, + NetPacketConn: bufio.NewPacketConn(conn), + remoteAddr: remoteAddr, + underlying: underlying, } } func NewAssociateConn(conn net.Conn, remoteAddr M.Socksaddr, underlying net.Conn) *AssociatePacketConn { return &AssociatePacketConn{ - PacketConn: bufio.NewUnbindPacketConn(conn), - remoteAddr: remoteAddr, - underlying: underlying, + NetPacketConn: bufio.NewUnbindPacketConn(conn), + remoteAddr: remoteAddr, + underlying: underlying, } } @@ -46,7 +46,7 @@ func (c *AssociatePacketConn) RemoteAddr() net.Addr { //warn:unsafe func (c *AssociatePacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { buffer := buf.With(p) - n, _, err = bufio.ReadFrom(c.PacketConn, buffer) + n, _, err = bufio.ReadFrom(c.NetPacketConn, buffer) if err != nil { return } @@ -76,7 +76,7 @@ func (c *AssociatePacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error if err != nil { return } - return bufio.WriteTo(c.PacketConn, buffer, c.addr) + return bufio.WriteTo(c.NetPacketConn, buffer, c.addr) } func (c *AssociatePacketConn) Read(b []byte) (n int, err error) { @@ -89,7 +89,7 @@ func (c *AssociatePacketConn) Write(b []byte) (n int, err error) { } func (c *AssociatePacketConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) { - _, addr, err := bufio.ReadFrom(c.PacketConn, buffer) + _, addr, err := bufio.ReadFrom(c.NetPacketConn, buffer) if err != nil { return M.Socksaddr{}, err } @@ -103,11 +103,11 @@ func (c *AssociatePacketConn) WritePacket(buffer *buf.Buffer, destination M.Sock header := buf.With(buffer.ExtendHeader(3 + M.SocksaddrSerializer.AddrPortLen(destination))) common.Must(header.WriteZeroN(3)) common.Must(M.SocksaddrSerializer.WriteAddrPort(header, destination)) - return common.Error(bufio.WriteTo(c.PacketConn, buffer, c.addr)) + return common.Error(bufio.WriteTo(c.NetPacketConn, buffer, c.addr)) } func (c *AssociatePacketConn) Upstream() any { - return c.PacketConn + return c.NetPacketConn } func (c *AssociatePacketConn) FrontHeadroom() int { @@ -116,7 +116,7 @@ func (c *AssociatePacketConn) FrontHeadroom() int { func (c *AssociatePacketConn) Close() error { return common.Close( - c.PacketConn, + c.NetPacketConn, c.underlying, ) } diff --git a/protocol/socks/packet_vectorised.go b/protocol/socks/packet_vectorised.go new file mode 100644 index 0000000..78bf918 --- /dev/null +++ b/protocol/socks/packet_vectorised.go @@ -0,0 +1,54 @@ +package socks + +import ( + "net" + + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +var _ N.VectorisedPacketWriter = (*VectorisedAssociatePacketConn)(nil) + +type VectorisedAssociatePacketConn struct { + AssociatePacketConn + N.VectorisedPacketWriter +} + +func NewVectorisedAssociatePacketConn(conn net.PacketConn, writer N.VectorisedPacketWriter, remoteAddr M.Socksaddr, underlying net.Conn) *VectorisedAssociatePacketConn { + return &VectorisedAssociatePacketConn{ + AssociatePacketConn{ + NetPacketConn: bufio.NewPacketConn(conn), + remoteAddr: remoteAddr, + underlying: underlying, + }, + writer, + } +} + +func NewVectorisedAssociateConn(conn net.Conn, writer N.VectorisedWriter, remoteAddr M.Socksaddr, underlying net.Conn) *VectorisedAssociatePacketConn { + return &VectorisedAssociatePacketConn{ + AssociatePacketConn{ + NetPacketConn: bufio.NewUnbindPacketConn(conn), + remoteAddr: remoteAddr, + underlying: underlying, + }, + &bufio.UnbindVectorisedPacketWriter{VectorisedWriter: writer}, + } +} + +func (v *VectorisedAssociatePacketConn) WriteVectorisedPacket(buffers []*buf.Buffer, destination M.Socksaddr) error { + _header := buf.StackNewSize(3 + M.SocksaddrSerializer.AddrPortLen(destination)) + defer common.KeepAlive(_header) + header := common.Dup(_header) + defer header.Release() + common.Must(header.WriteZeroN(3)) + common.Must(M.SocksaddrSerializer.WriteAddrPort(header, destination)) + return v.VectorisedPacketWriter.WriteVectorisedPacket(append([]*buf.Buffer{header}, buffers...), destination) +} + +func (c *VectorisedAssociatePacketConn) FrontHeadroom() int { + return 0 +}