mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-01-12 21:57:15 +08:00
https://github.com/XTLS/Xray-core/pull/5509#issuecomment-3732898130 --------- Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> Co-authored-by: Fangliding <45535409+Fangliding@users.noreply.github.com> Co-authored-by: Owersun <4807375+Owersun@users.noreply.github.com>
211 lines
5.9 KiB
Go
211 lines
5.9 KiB
Go
package tun
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/xtls/xray-core/common/errors"
|
|
"github.com/xtls/xray-core/common/net"
|
|
"gvisor.dev/gvisor/pkg/buffer"
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
|
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
|
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
|
"gvisor.dev/gvisor/pkg/waiter"
|
|
)
|
|
|
|
const (
|
|
defaultNIC tcpip.NICID = 1
|
|
|
|
tcpRXBufMinSize = tcp.MinBufferSize
|
|
tcpRXBufDefSize = tcp.DefaultSendBufferSize
|
|
tcpRXBufMaxSize = 8 << 20 // 8MiB
|
|
|
|
tcpTXBufMinSize = tcp.MinBufferSize
|
|
tcpTXBufDefSize = tcp.DefaultReceiveBufferSize
|
|
tcpTXBufMaxSize = 6 << 20 // 6MiB
|
|
)
|
|
|
|
// stackGVisor is ip stack implemented by gVisor package
|
|
type stackGVisor struct {
|
|
ctx context.Context
|
|
tun GVisorTun
|
|
idleTimeout time.Duration
|
|
handler *Handler
|
|
stack *stack.Stack
|
|
endpoint stack.LinkEndpoint
|
|
}
|
|
|
|
// GVisorTun implements a bridge to connect gVisor ip stack to tun interface
|
|
type GVisorTun interface {
|
|
newEndpoint() (stack.LinkEndpoint, error)
|
|
}
|
|
|
|
// NewStack builds new ip stack (using gVisor)
|
|
func NewStack(ctx context.Context, options StackOptions, handler *Handler) (Stack, error) {
|
|
gStack := &stackGVisor{
|
|
ctx: ctx,
|
|
tun: options.Tun.(GVisorTun),
|
|
idleTimeout: options.IdleTimeout,
|
|
handler: handler,
|
|
}
|
|
|
|
return gStack, nil
|
|
}
|
|
|
|
// Start is called by Handler to bring stack to life
|
|
func (t *stackGVisor) Start() error {
|
|
linkEndpoint, err := t.tun.newEndpoint()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ipStack, err := createStack(linkEndpoint)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tcpForwarder := tcp.NewForwarder(ipStack, 0, 65535, func(r *tcp.ForwarderRequest) {
|
|
go func(r *tcp.ForwarderRequest) {
|
|
var wq waiter.Queue
|
|
var id = r.ID()
|
|
|
|
// Perform a TCP three-way handshake.
|
|
ep, err := r.CreateEndpoint(&wq)
|
|
if err != nil {
|
|
errors.LogError(t.ctx, err.String())
|
|
r.Complete(true)
|
|
return
|
|
}
|
|
|
|
options := ep.SocketOptions()
|
|
options.SetKeepAlive(false)
|
|
options.SetReuseAddress(true)
|
|
options.SetReusePort(true)
|
|
|
|
t.handler.HandleConnection(
|
|
gonet.NewTCPConn(&wq, ep),
|
|
// local address on the gVisor side is connection destination
|
|
net.TCPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)),
|
|
)
|
|
|
|
// close the socket
|
|
ep.Close()
|
|
// send connection complete upstream
|
|
r.Complete(false)
|
|
}(r)
|
|
})
|
|
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
|
|
|
|
// Use custom UDP packet handler, instead of strict gVisor forwarder, for FullCone NAT support
|
|
udpForwarder := newUdpConnectionHandler(t.ctx, t.handler, func(p []byte) {
|
|
// extract network protocol from the packet
|
|
var networkProtocol tcpip.NetworkProtocolNumber
|
|
switch header.IPVersion(p) {
|
|
case header.IPv4Version:
|
|
networkProtocol = header.IPv4ProtocolNumber
|
|
case header.IPv6Version:
|
|
networkProtocol = header.IPv6ProtocolNumber
|
|
default:
|
|
// discard packet with unknown network version
|
|
return
|
|
}
|
|
|
|
ipStack.WriteRawPacket(defaultNIC, networkProtocol, buffer.MakeWithData(p))
|
|
})
|
|
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
|
|
data := pkt.Data().AsRange().ToSlice()
|
|
if len(data) == 0 {
|
|
return false
|
|
}
|
|
// source/destination of the packet we process as incoming, on gVisor side are Remote/Local
|
|
// in other terms, src is the side behind tun, dst is the side behind gVisor
|
|
// this function handle packets passing from the tun to the gVisor, therefore the src/dst assignement
|
|
src := net.UDPDestination(net.IPAddress(id.RemoteAddress.AsSlice()), net.Port(id.RemotePort))
|
|
dst := net.UDPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort))
|
|
|
|
return udpForwarder.HandlePacket(src, dst, data)
|
|
})
|
|
|
|
t.stack = ipStack
|
|
t.endpoint = linkEndpoint
|
|
|
|
return nil
|
|
}
|
|
|
|
// Close is called by Handler to shut down the stack
|
|
func (t *stackGVisor) Close() error {
|
|
if t.stack == nil {
|
|
return nil
|
|
}
|
|
t.endpoint.Attach(nil)
|
|
t.stack.Close()
|
|
for _, endpoint := range t.stack.CleanupEndpoints() {
|
|
endpoint.Abort()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createStack configure gVisor ip stack
|
|
func createStack(ep stack.LinkEndpoint) (*stack.Stack, error) {
|
|
opts := stack.Options{
|
|
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
|
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol},
|
|
HandleLocal: false,
|
|
}
|
|
gStack := stack.New(opts)
|
|
|
|
err := gStack.CreateNIC(defaultNIC, ep)
|
|
if err != nil {
|
|
return nil, errors.New(err.String())
|
|
}
|
|
|
|
gStack.SetRouteTable([]tcpip.Route{
|
|
{Destination: header.IPv4EmptySubnet, NIC: defaultNIC},
|
|
{Destination: header.IPv6EmptySubnet, NIC: defaultNIC},
|
|
})
|
|
|
|
err = gStack.SetSpoofing(defaultNIC, true)
|
|
if err != nil {
|
|
return nil, errors.New(err.String())
|
|
}
|
|
err = gStack.SetPromiscuousMode(defaultNIC, true)
|
|
if err != nil {
|
|
return nil, errors.New(err.String())
|
|
}
|
|
|
|
cOpt := tcpip.CongestionControlOption("cubic")
|
|
gStack.SetTransportProtocolOption(tcp.ProtocolNumber, &cOpt)
|
|
sOpt := tcpip.TCPSACKEnabled(true)
|
|
gStack.SetTransportProtocolOption(tcp.ProtocolNumber, &sOpt)
|
|
mOpt := tcpip.TCPModerateReceiveBufferOption(true)
|
|
gStack.SetTransportProtocolOption(tcp.ProtocolNumber, &mOpt)
|
|
|
|
tcpRXBufOpt := tcpip.TCPReceiveBufferSizeRangeOption{
|
|
Min: tcpRXBufMinSize,
|
|
Default: tcpRXBufDefSize,
|
|
Max: tcpRXBufMaxSize,
|
|
}
|
|
err = gStack.SetTransportProtocolOption(tcp.ProtocolNumber, &tcpRXBufOpt)
|
|
if err != nil {
|
|
return nil, errors.New(err.String())
|
|
}
|
|
|
|
tcpTXBufOpt := tcpip.TCPSendBufferSizeRangeOption{
|
|
Min: tcpTXBufMinSize,
|
|
Default: tcpTXBufDefSize,
|
|
Max: tcpTXBufMaxSize,
|
|
}
|
|
err = gStack.SetTransportProtocolOption(tcp.ProtocolNumber, &tcpTXBufOpt)
|
|
if err != nil {
|
|
return nil, errors.New(err.String())
|
|
}
|
|
|
|
return gStack, nil
|
|
}
|