Merge tag 'v1.14.0'

This commit is contained in:
Shtorm
2026-09-04 01:08:30 +03:00
987 changed files with 141481 additions and 11302 deletions
+2 -2
View File
@@ -15,6 +15,7 @@ import (
"github.com/sagernet/quic-go/congestion"
"github.com/sagernet/quic-go/http3"
qtls "github.com/sagernet/sing-quic"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
aTLS "github.com/sagernet/sing/common/tls"
@@ -92,8 +93,7 @@ func ConnectTunnel(ctx context.Context, dialer N.Dialer, tlsConfig aTLS.Config,
}
conn, err := qtls.Dial(
ctx,
udpConn,
quicEndpoint,
bufio.NewBindPacketConn(udpConn, quicEndpoint),
tlsConfig,
quicConfig,
)
+206
View File
@@ -0,0 +1,206 @@
package openconnect
import (
"context"
"net/netip"
"sync/atomic"
"time"
"github.com/sagernet/sing-openconnect"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network"
)
const (
DefaultMTU = 1500
PacketHeadroom = openconnect.PacketHeadroom
)
type PacketWriter func(packetBuffers []*buf.Buffer) error
type Device interface {
N.Dialer
Start() error
UpdateConfiguration(configuration Configuration) error
WriteInboundBuffers(packetBuffers []*buf.Buffer) error
SetPacketWriter(writer PacketWriter)
PortAddresses() (netip.Addr, netip.Addr)
PortMTU() uint32
AttachReturn(returnPath tun.Return) error
DetachReturn(returnPath tun.Return) error
ReturnPath() (tun.Return, int)
Close() error
}
type DeviceOptions struct {
Context context.Context
Logger logger.ContextLogger
System bool
Handler tun.Handler
UDPTimeout time.Duration
ICMPTimeout time.Duration
UDPMapping tun.NATMapping
UDPFiltering tun.NATFiltering
UDPNATMax uint32
InterfaceFinder control.InterfaceFinder
ExcludeInterface []string
Name string
MTU uint32
Configuration Configuration
}
type Configuration struct {
MTU uint32
Addresses []netip.Prefix
Routes []Route
ExcludedRoutes []Route
DNS []netip.Addr
NBNS []netip.Addr
SearchDomains []string
SplitDNS []string
SplitDNSRules []SplitDNSRule
ProxyAutoConfigURL string
Banner string
TunnelAllDNS bool
ClientBypassProtocol bool
IdleTimeout time.Duration
AuthenticationExpiration time.Time
}
type Route struct {
Prefix netip.Prefix
Gateway netip.Addr
Metric int
}
type SplitDNSRule struct {
Domains []string
Servers []netip.Addr
}
func NewDevice(options DeviceOptions) (Device, error) {
if !options.System {
return newStackDevice(options)
}
if !tun.WithGVisor {
return newSystemDevice(options)
}
return newSystemStackDevice(options)
}
type baseDevice struct {
packetWriter PacketWriter
returnState atomic.Pointer[returnPathState]
}
func (d *baseDevice) SetPacketWriter(writer PacketWriter) {
d.packetWriter = writer
}
func (d *baseDevice) writeOutbound(packetBuffers []*buf.Buffer) error {
if d.packetWriter == nil {
buf.ReleaseMulti(packetBuffers)
return E.New("missing packet writer")
}
return d.packetWriter(packetBuffers)
}
func (d *baseDevice) processInboundBuffers(packetBuffers []*buf.Buffer, writeBuffers func(packetBuffers []*buf.Buffer) error) error {
if len(packetBuffers) == 0 {
return nil
}
state := d.returnState.Load()
if state == nil {
return writeBuffers(packetBuffers)
}
packets := make([][]byte, len(packetBuffers))
for i, packetBuffer := range packetBuffers {
packetBuffer.ExtendHeader(state.headroom)
packets[i] = packetBuffer.Bytes()
}
unconsumed := state.returnPath.ReturnPackets(packets)
if len(unconsumed) == 0 {
return nil
}
unconsumedBuffers := make([]*buf.Buffer, len(unconsumed))
for i, packet := range unconsumed {
packetBuffer := buf.As(packet)
packetBuffer.Advance(state.headroom)
unconsumedBuffers[i] = packetBuffer
}
return writeBuffers(unconsumedBuffers)
}
func (d *baseDevice) AttachReturn(returnPath tun.Return) error {
headroom := returnPath.ReturnHeadroom()
if headroom > PacketHeadroom {
return E.New("return path headroom ", headroom, " exceeds available ", PacketHeadroom)
}
newState := &returnPathState{
returnPath: returnPath,
headroom: headroom,
}
for {
currentState := d.returnState.Load()
if currentState != nil {
if currentState.returnPath == returnPath {
return nil
}
return E.New("return path already attached")
}
if d.returnState.CompareAndSwap(nil, newState) {
return nil
}
}
}
func (d *baseDevice) DetachReturn(returnPath tun.Return) error {
currentState := d.returnState.Load()
if currentState != nil && currentState.returnPath == returnPath {
d.returnState.CompareAndSwap(currentState, nil)
}
return nil
}
func (d *baseDevice) ReturnPath() (tun.Return, int) {
state := d.returnState.Load()
if state == nil {
return nil, 0
}
return state.returnPath, state.headroom
}
type returnPathState struct {
returnPath tun.Return
headroom int
}
func firstAddresses(addresses []netip.Prefix) (netip.Addr, netip.Addr) {
var inet4Address netip.Addr
var inet6Address netip.Addr
for _, prefix := range addresses {
if prefix.Addr().Is4() && !inet4Address.IsValid() {
inet4Address = prefix.Addr()
} else if prefix.Addr().Is6() && !inet6Address.IsValid() {
inet6Address = prefix.Addr()
}
}
return inet4Address, inet6Address
}
func splitPrefixes(prefixes []netip.Prefix) ([]netip.Prefix, []netip.Prefix) {
var inet4Prefixes []netip.Prefix
var inet6Prefixes []netip.Prefix
for _, prefix := range prefixes {
if prefix.Addr().Is4() {
inet4Prefixes = append(inet4Prefixes, prefix)
} else {
inet6Prefixes = append(inet6Prefixes, prefix)
}
}
return inet4Prefixes, inet6Prefixes
}
+359
View File
@@ -0,0 +1,359 @@
//go:build with_gvisor
package openconnect
import (
"context"
"net"
"net/netip"
"slices"
"sync"
"sync/atomic"
"github.com/sagernet/gvisor/pkg/buffer"
"github.com/sagernet/gvisor/pkg/tcpip"
"github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet"
"github.com/sagernet/gvisor/pkg/tcpip/header"
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv4"
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv6"
"github.com/sagernet/gvisor/pkg/tcpip/stack"
"github.com/sagernet/gvisor/pkg/tcpip/transport/icmp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
var _ Device = (*stackDevice)(nil)
type stackDevice struct {
baseDevice
stateAccess sync.RWMutex
options DeviceOptions
stack *stack.Stack
endpoint *stackEndpoint
inet4Address netip.Addr
inet6Address netip.Addr
udpForwarder *tun.UDPForwarder
icmpForwarder *tun.ICMPForwarder
closeOnce sync.Once
}
func newStackDevice(options DeviceOptions) (*stackDevice, error) {
if options.MTU == 0 {
options.MTU = DefaultMTU
}
device := &stackDevice{
options: options,
}
endpoint := &stackEndpoint{
device: device,
done: make(chan struct{}),
}
endpoint.mtu.Store(options.MTU)
ipStack, err := tun.NewGVisorStackWithOptions(endpoint, stack.NICOptions{}, true)
if err != nil {
return nil, err
}
device.stack = ipStack
device.endpoint = endpoint
err = device.updateAddresses(nil, options.Configuration.Addresses)
if err != nil {
ipStack.Close()
return nil, err
}
if options.Handler != nil {
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket)
udpForwarder := tun.NewUDPForwarder(options.Context, ipStack, options.Handler, tun.UDPNatOptions{
Timeout: options.UDPTimeout,
Shared: true,
Mapping: options.UDPMapping,
Filtering: options.UDPFiltering,
MaxSize: options.UDPNATMax,
InterfaceFinder: options.InterfaceFinder,
ExcludeInterface: options.ExcludeInterface,
})
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)
device.udpForwarder = udpForwarder
icmpForwarder := tun.NewICMPForwarder(ipStack, options.Handler, options.Logger)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
device.icmpForwarder = icmpForwarder
}
return device, nil
}
func (d *stackDevice) Start() error {
if d.udpForwarder != nil {
err := d.udpForwarder.Start()
if err != nil {
return err
}
}
return nil
}
func (d *stackDevice) UpdateConfiguration(configuration Configuration) error {
d.stateAccess.Lock()
defer d.stateAccess.Unlock()
if configuration.MTU != 0 {
d.options.MTU = configuration.MTU
d.endpoint.mtu.Store(configuration.MTU)
}
previousAddresses := d.options.Configuration.Addresses
d.options.Configuration = configuration
return d.updateAddresses(previousAddresses, configuration.Addresses)
}
func (d *stackDevice) updateAddresses(previousAddresses []netip.Prefix, addresses []netip.Prefix) error {
for _, prefix := range previousAddresses {
if slices.Contains(addresses, prefix) {
continue
}
gErr := d.stack.RemoveAddress(tun.DefaultNIC, tun.AddressFromAddr(prefix.Addr()))
if gErr != nil {
return E.New("remove local address ", prefix, ": ", gErr.String())
}
}
for _, prefix := range addresses {
if slices.Contains(previousAddresses, prefix) {
continue
}
protocolAddress := tcpip.ProtocolAddress{
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: tun.AddressFromAddr(prefix.Addr()),
PrefixLen: prefix.Bits(),
},
}
if prefix.Addr().Is4() {
protocolAddress.Protocol = ipv4.ProtocolNumber
} else {
protocolAddress.Protocol = ipv6.ProtocolNumber
}
gErr := d.stack.AddProtocolAddress(tun.DefaultNIC, protocolAddress, stack.AddressProperties{})
if gErr != nil {
return E.New("add local address ", prefix, ": ", gErr.String())
}
}
d.inet4Address, d.inet6Address = firstAddresses(addresses)
return nil
}
func (d *stackDevice) WriteInboundBuffers(packetBuffers []*buf.Buffer) error {
return d.processInboundBuffers(packetBuffers, d.writeBuffers)
}
func (d *stackDevice) writeBuffers(packetBuffers []*buf.Buffer) error {
networkProtocols := make([]tcpip.NetworkProtocolNumber, 0, len(packetBuffers))
stackPacketBuffers := make([]*stack.PacketBuffer, 0, len(packetBuffers))
var packetErr error
for _, packetBuffer := range packetBuffers {
packet := packetBuffer.Bytes()
var networkProtocol tcpip.NetworkProtocolNumber
switch header.IPVersion(packet) {
case header.IPv4Version:
networkProtocol = header.IPv4ProtocolNumber
case header.IPv6Version:
networkProtocol = header.IPv6ProtocolNumber
default:
if packetErr == nil {
packetErr = E.New("invalid IP packet")
}
continue
}
networkProtocols = append(networkProtocols, networkProtocol)
stackPacketBuffers = append(stackPacketBuffers, stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: buffer.MakeWithData(packet),
}))
}
d.endpoint.deliverNetworkPackets(networkProtocols, stackPacketBuffers)
for _, packetBuffer := range stackPacketBuffers {
packetBuffer.DecRef()
}
return packetErr
}
func (d *stackDevice) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
inet4Address, inet6Address := d.PortAddresses()
address := tcpip.FullAddress{
NIC: tun.DefaultNIC,
Port: destination.Port,
Addr: tun.AddressFromAddr(destination.Addr),
}
bind := tcpip.FullAddress{
NIC: tun.DefaultNIC,
}
var networkProtocol tcpip.NetworkProtocolNumber
if destination.IsIPv4() {
if !inet4Address.IsValid() {
return nil, E.New("missing IPv4 local address")
}
networkProtocol = header.IPv4ProtocolNumber
bind.Addr = tun.AddressFromAddr(inet4Address)
} else {
if !inet6Address.IsValid() {
return nil, E.New("missing IPv6 local address")
}
networkProtocol = header.IPv6ProtocolNumber
bind.Addr = tun.AddressFromAddr(inet6Address)
}
switch N.NetworkName(network) {
case N.NetworkTCP:
return gonet.DialTCPWithBind(ctx, d.stack, bind, address, networkProtocol)
case N.NetworkUDP:
return gonet.DialUDP(d.stack, &bind, &address, networkProtocol)
default:
return nil, E.Extend(N.ErrUnknownNetwork, network)
}
}
func (d *stackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
inet4Address, inet6Address := d.PortAddresses()
bind := tcpip.FullAddress{
NIC: tun.DefaultNIC,
}
var networkProtocol tcpip.NetworkProtocolNumber
if destination.IsIPv4() {
if !inet4Address.IsValid() {
return nil, E.New("missing IPv4 local address")
}
networkProtocol = header.IPv4ProtocolNumber
bind.Addr = tun.AddressFromAddr(inet4Address)
} else {
if !inet6Address.IsValid() {
return nil, E.New("missing IPv6 local address")
}
networkProtocol = header.IPv6ProtocolNumber
bind.Addr = tun.AddressFromAddr(inet6Address)
}
return gonet.DialUDP(d.stack, &bind, nil, networkProtocol)
}
func (d *stackDevice) PortAddresses() (netip.Addr, netip.Addr) {
d.stateAccess.RLock()
defer d.stateAccess.RUnlock()
return d.inet4Address, d.inet6Address
}
func (d *stackDevice) PortMTU() uint32 {
d.stateAccess.RLock()
defer d.stateAccess.RUnlock()
return d.options.MTU
}
func (d *stackDevice) Close() error {
d.closeOnce.Do(func() {
close(d.endpoint.done)
if d.udpForwarder != nil {
d.udpForwarder.Close()
}
if d.icmpForwarder != nil {
d.icmpForwarder.Close()
}
d.stack.Close()
for _, endpoint := range d.stack.CleanupEndpoints() {
endpoint.Abort()
}
d.stack.Wait()
})
return nil
}
type stackEndpoint struct {
device *stackDevice
mtu atomic.Uint32
done chan struct{}
dispatcherAccess sync.RWMutex
dispatcher stack.NetworkDispatcher
}
func (e *stackEndpoint) MTU() uint32 {
return e.mtu.Load()
}
func (e *stackEndpoint) SetMTU(mtu uint32) {
e.mtu.Store(mtu)
}
func (e *stackEndpoint) MaxHeaderLength() uint16 {
return 0
}
func (e *stackEndpoint) LinkAddress() tcpip.LinkAddress {
return ""
}
func (e *stackEndpoint) SetLinkAddress(addr tcpip.LinkAddress) {
}
func (e *stackEndpoint) Capabilities() stack.LinkEndpointCapabilities {
return stack.CapabilityRXChecksumOffload
}
func (e *stackEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
e.dispatcherAccess.Lock()
defer e.dispatcherAccess.Unlock()
e.dispatcher = dispatcher
}
func (e *stackEndpoint) IsAttached() bool {
e.dispatcherAccess.RLock()
defer e.dispatcherAccess.RUnlock()
return e.dispatcher != nil
}
func (e *stackEndpoint) deliverNetworkPackets(networkProtocols []tcpip.NetworkProtocolNumber, packetBuffers []*stack.PacketBuffer) {
e.dispatcherAccess.RLock()
defer e.dispatcherAccess.RUnlock()
if e.dispatcher == nil {
return
}
for i, packetBuffer := range packetBuffers {
e.dispatcher.DeliverNetworkPacket(networkProtocols[i], packetBuffer)
}
}
func (e *stackEndpoint) Wait() {
}
func (e *stackEndpoint) ARPHardwareType() header.ARPHardwareType {
return header.ARPHardwareNone
}
func (e *stackEndpoint) AddHeader(packetBuffer *stack.PacketBuffer) {
}
func (e *stackEndpoint) ParseHeader(packetBuffer *stack.PacketBuffer) bool {
return true
}
func (e *stackEndpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Error) {
packetBuffers := make([]*buf.Buffer, 0, list.Len())
for _, packetBuffer := range list.AsSlice() {
packetSlices := packetBuffer.AsSlices()
packetLength := 0
for _, packetSlice := range packetSlices {
packetLength += len(packetSlice)
}
outboundBuffer := buf.NewSize(PacketHeadroom + packetLength + systemDevicePacketRearSpace)
outboundBuffer.Resize(PacketHeadroom, 0)
for _, packetSlice := range packetSlices {
_, _ = outboundBuffer.Write(packetSlice)
}
packetBuffers = append(packetBuffers, outboundBuffer)
}
err := e.device.writeOutbound(packetBuffers)
if err != nil {
return 0, &tcpip.ErrClosedForSend{}
}
return list.Len(), nil
}
func (e *stackEndpoint) Close() {
}
func (e *stackEndpoint) SetOnCloseAction(action func()) {
}
@@ -0,0 +1,13 @@
//go:build !with_gvisor
package openconnect
import E "github.com/sagernet/sing/common/exceptions"
func newStackDevice(options DeviceOptions) (Device, error) {
return nil, E.New("system:false requires the with_gvisor build tag")
}
func newSystemStackDevice(options DeviceOptions) (Device, error) {
return nil, E.New("system stack requires the with_gvisor build tag")
}
+361
View File
@@ -0,0 +1,361 @@
package openconnect
import (
"context"
"net"
"net/netip"
"slices"
"sync"
"syscall"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/gtcpip/header"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
)
var _ Device = (*systemDevice)(nil)
const (
systemDeviceReadBufferSize = 65535 + tun.PacketOffset
systemDevicePacketRearSpace = 64
)
type systemDevice struct {
baseDevice
stateAccess sync.RWMutex
options DeviceOptions
dialer N.Dialer
device tun.Tun
inet4Address netip.Addr
inet6Address netip.Addr
closed bool
}
func newSystemDevice(options DeviceOptions) (*systemDevice, error) {
if options.Name == "" {
options.Name = tun.CalculateInterfaceName("oc")
}
if options.MTU == 0 {
options.MTU = DefaultMTU
}
interfaceDialer, err := dialer.NewDefault(options.Context, option.DialerOptions{
AbstractDialerOptions: option.AbstractDialerOptions{
BindInterface: options.Name,
},
})
if err != nil {
return nil, err
}
inet4Address, inet6Address := firstAddresses(options.Configuration.Addresses)
return &systemDevice{
options: options,
dialer: interfaceDialer,
inet4Address: inet4Address,
inet6Address: inet6Address,
}, nil
}
func (d *systemDevice) Start() error {
d.stateAccess.Lock()
defer d.stateAccess.Unlock()
return d.startLocked()
}
func (d *systemDevice) startLocked() error {
if d.closed {
return net.ErrClosed
}
if d.device != nil {
return nil
}
tunOptions := d.buildTunOptions()
tunInterface, err := tun.New(tunOptions)
if err != nil {
return err
}
err = tunInterface.Start()
if err != nil {
tunInterface.Close()
return err
}
d.device = tunInterface
d.options.Logger.Info("started at ", d.options.Name)
go d.readLoop(tunInterface, int(d.options.MTU))
return nil
}
func (d *systemDevice) buildTunOptions() tun.Options {
inet4Address, inet6Address := firstAddresses(d.options.Configuration.Addresses)
d.inet4Address = inet4Address
d.inet6Address = inet6Address
inet4Addresses, inet6Addresses := splitPrefixes(d.options.Configuration.Addresses)
networkManager := service.FromContext[adapter.NetworkManager](d.options.Context)
tunOptions := tun.Options{
Name: d.options.Name,
Inet4Address: inet4Addresses,
Inet6Address: inet6Addresses,
MTU: d.options.MTU,
GSO: true,
InterfaceScope: true,
DNSMode: tun.DNSModeDisabled,
InterfaceMonitor: nil,
InterfaceFinder: nil,
Logger: d.options.Logger,
IPRoute2TableIndex: tun.DefaultIPRoute2TableIndex,
IPRoute2RuleIndex: tun.DefaultIPRoute2RuleIndex,
EXP_DisableDNSHijack: true,
}
if networkManager != nil {
tunOptions.InterfaceMonitor = networkManager.InterfaceMonitor()
tunOptions.InterfaceFinder = networkManager.InterfaceFinder()
}
return tunOptions
}
func (d *systemDevice) readLoop(tunInterface tun.Tun, mtu int) {
linuxTUN, isLinuxTUN := tunInterface.(tun.LinuxTUN)
if isLinuxTUN && linuxTUN.BatchSize() > 1 {
d.readLoopLinux(linuxTUN, linuxTUN.BatchSize(), mtu)
return
}
darwinTUN, isDarwinTUN := tunInterface.(tun.DarwinTUN)
if isDarwinTUN {
d.readLoopDarwin(darwinTUN)
return
}
packetBuffer := buf.NewSize(PacketHeadroom + systemDeviceReadBufferSize + systemDevicePacketRearSpace)
defer packetBuffer.Release()
for {
packetBuffer.Reset()
packetBuffer.Resize(PacketHeadroom, 0)
readN, err := tunInterface.Read(packetBuffer.FreeBytes()[:systemDeviceReadBufferSize])
if err != nil {
if E.IsClosed(err) {
return
}
d.options.Logger.Error(E.Cause(err, "read packet"))
return
}
if readN <= tun.PacketOffset {
continue
}
packetBuffer.Truncate(readN)
packetBuffer.Advance(tun.PacketOffset)
packetBuffer.IncRef()
err = d.writeOutbound([]*buf.Buffer{packetBuffer})
packetBuffer.DecRef()
if err != nil {
d.options.Logger.Error(E.Cause(err, "write packet"))
return
}
}
}
func (d *systemDevice) readLoopLinux(tunInterface tun.LinuxTUN, batchSize int, mtu int) {
packetBuffers := make([]*buf.Buffer, batchSize)
readBuffers := make([][]byte, batchSize)
packetSizes := make([]int, batchSize)
for i := range packetBuffers {
packetBuffers[i] = buf.NewSize(PacketHeadroom + mtu + systemDevicePacketRearSpace)
}
defer buf.ReleaseMulti(packetBuffers)
for {
for i, packetBuffer := range packetBuffers {
packetBuffer.Reset()
packetBuffer.Resize(PacketHeadroom, 0)
readBuffers[i] = packetBuffer.FreeBytes()[:mtu]
}
packetCount, readErr := tunInterface.BatchRead(readBuffers, 0, packetSizes)
for i := range packetCount {
packetBuffers[i].Truncate(packetSizes[i])
packetBuffers[i].IncRef()
}
if packetCount > 0 {
writeErr := d.writeOutbound(packetBuffers[:packetCount])
for i := range packetCount {
packetBuffers[i].DecRef()
}
if writeErr != nil {
d.options.Logger.Error(E.Cause(writeErr, "write packet batch"))
return
}
}
if readErr != nil {
if E.IsClosed(readErr) {
return
}
d.options.Logger.Error(E.Cause(readErr, "batch read packet"))
return
}
}
}
func (d *systemDevice) readLoopDarwin(tunInterface tun.DarwinTUN) {
for {
packetBuffers, readErr := tunInterface.BatchRead()
outboundBuffers := packetBuffers[:0]
for _, packetBuffer := range packetBuffers {
if packetBuffer.IsEmpty() {
packetBuffer.Release()
continue
}
outboundBuffers = append(outboundBuffers, packetBuffer)
}
if len(outboundBuffers) > 0 {
writeErr := d.writeOutbound(outboundBuffers)
if writeErr != nil {
d.options.Logger.Error(E.Cause(writeErr, "write packet batch"))
return
}
}
if readErr != nil {
if E.IsClosed(readErr) || E.IsMulti(readErr, syscall.EBADF) {
return
}
d.options.Logger.Error(E.Cause(readErr, "batch read packet"))
return
}
}
}
func (d *systemDevice) UpdateConfiguration(configuration Configuration) error {
d.stateAccess.Lock()
defer d.stateAccess.Unlock()
previousConfiguration := d.options.Configuration
previousMTU := d.options.MTU
updatedMTU := d.options.MTU
if configuration.MTU != 0 {
updatedMTU = configuration.MTU
}
d.options.MTU = updatedMTU
d.options.Configuration = configuration
if d.device == nil {
inet4Address, inet6Address := firstAddresses(configuration.Addresses)
d.inet4Address = inet4Address
d.inet6Address = inet6Address
return nil
}
if !slices.Equal(previousConfiguration.Addresses, configuration.Addresses) ||
previousMTU != updatedMTU {
d.device.Close()
d.device = nil
return d.startLocked()
}
return nil
}
func (d *systemDevice) WriteInboundBuffers(packetBuffers []*buf.Buffer) error {
return d.processInboundBuffers(packetBuffers, d.writeBuffers)
}
func (d *systemDevice) writeBuffers(packetBuffers []*buf.Buffer) error {
d.stateAccess.RLock()
tunInterface := d.device
d.stateAccess.RUnlock()
if tunInterface == nil {
return E.New("system device is not ready")
}
linuxTUN, isLinuxTUN := tunInterface.(tun.LinuxTUN)
if isLinuxTUN {
headroom := linuxTUN.FrontHeadroom()
packets := make([][]byte, len(packetBuffers))
var temporaryBuffers []*buf.Buffer
for i, packetBuffer := range packetBuffers {
if packetBuffer.Start() >= headroom {
packetBuffer.ExtendHeader(headroom)
packets[i] = packetBuffer.Bytes()
packetBuffer.Advance(headroom)
continue
}
temporaryBuffer := buf.NewSize(headroom + packetBuffer.Len())
temporaryBuffer.Resize(headroom, 0)
_, _ = temporaryBuffer.Write(packetBuffer.Bytes())
temporaryBuffer.ExtendHeader(headroom)
packets[i] = temporaryBuffer.Bytes()
temporaryBuffers = append(temporaryBuffers, temporaryBuffer)
}
_, err := linuxTUN.BatchWrite(packets, headroom)
buf.ReleaseMulti(temporaryBuffers)
return err
}
darwinTUN, isDarwinTUN := tunInterface.(tun.DarwinTUN)
if isDarwinTUN {
return darwinTUN.BatchWrite(packetBuffers)
}
for _, packetBuffer := range packetBuffers {
err := d.writePacket(packetBuffer.Bytes())
if err != nil {
return err
}
}
return nil
}
func (d *systemDevice) writePacket(packet []byte) error {
d.stateAccess.RLock()
tunInterface := d.device
d.stateAccess.RUnlock()
if tunInterface == nil {
return E.New("system device is not ready")
}
if tun.PacketOffset == 0 {
_, err := tunInterface.Write(packet)
return err
}
writeBuffer := make([]byte, tun.PacketOffset+len(packet))
tun.PacketFillHeader(writeBuffer[:tun.PacketOffset], header.IPVersion(packet))
copy(writeBuffer[tun.PacketOffset:], packet)
_, err := tunInterface.Write(writeBuffer)
return err
}
func (d *systemDevice) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if !destination.Addr.IsValid() {
return nil, E.New("invalid non-IP destination")
}
return d.dialer.DialContext(ctx, network, destination)
}
func (d *systemDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if !destination.Addr.IsValid() {
return nil, E.New("invalid non-IP destination")
}
return d.dialer.ListenPacket(ctx, destination)
}
func (d *systemDevice) PortAddresses() (netip.Addr, netip.Addr) {
d.stateAccess.RLock()
defer d.stateAccess.RUnlock()
return d.inet4Address, d.inet6Address
}
func (d *systemDevice) PortMTU() uint32 {
d.stateAccess.RLock()
defer d.stateAccess.RUnlock()
return d.options.MTU
}
func (d *systemDevice) Close() error {
d.stateAccess.Lock()
defer d.stateAccess.Unlock()
d.closed = true
if d.device == nil {
return nil
}
err := d.device.Close()
d.device = nil
return err
}
func (d *systemDevice) configurationAddresses() []netip.Prefix {
d.stateAccess.RLock()
defer d.stateAccess.RUnlock()
return slices.Clone(d.options.Configuration.Addresses)
}
@@ -0,0 +1,116 @@
//go:build with_gvisor
package openconnect
import (
"net/netip"
"github.com/sagernet/sing-tun/gtcpip/header"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
)
var _ Device = (*systemStackDevice)(nil)
type systemStackDevice struct {
*systemDevice
stackDevice *stackDevice
}
func newSystemStackDevice(options DeviceOptions) (*systemStackDevice, error) {
system, err := newSystemDevice(options)
if err != nil {
return nil, err
}
stackOptions := options
stackOptions.System = false
stackOptions.Name = system.options.Name
stackOptions.ExcludeInterface = []string{system.options.Name}
stackDevice, err := newStackDevice(stackOptions)
if err != nil {
system.Close()
return nil, err
}
return &systemStackDevice{
systemDevice: system,
stackDevice: stackDevice,
}, nil
}
func (d *systemStackDevice) SetPacketWriter(writer PacketWriter) {
d.systemDevice.SetPacketWriter(writer)
d.stackDevice.SetPacketWriter(writer)
}
func (d *systemStackDevice) Start() error {
err := d.stackDevice.Start()
if err != nil {
return err
}
err = d.systemDevice.Start()
return err
}
func (d *systemStackDevice) UpdateConfiguration(configuration Configuration) error {
err := d.systemDevice.UpdateConfiguration(configuration)
if err != nil {
return err
}
return d.stackDevice.UpdateConfiguration(configuration)
}
func (d *systemStackDevice) WriteInboundBuffers(packetBuffers []*buf.Buffer) error {
return d.systemDevice.processInboundBuffers(packetBuffers, d.writeBuffers)
}
func (d *systemStackDevice) writeBuffers(packetBuffers []*buf.Buffer) error {
addresses := d.systemDevice.configurationAddresses()
runStart := 0
runUsesSystemDevice := false
var writeErr error
for i, packetBuffer := range packetBuffers {
destination := packetDestination(packetBuffer.Bytes())
useSystemDevice := false
for _, prefix := range addresses {
if prefix.Contains(destination) {
useSystemDevice = true
break
}
}
if i > runStart && useSystemDevice != runUsesSystemDevice {
var err error
if runUsesSystemDevice {
err = d.systemDevice.writeBuffers(packetBuffers[runStart:i])
} else {
err = d.stackDevice.writeBuffers(packetBuffers[runStart:i])
}
writeErr = E.Errors(writeErr, err)
runStart = i
}
if i == runStart {
runUsesSystemDevice = useSystemDevice
}
}
if runStart == len(packetBuffers) {
return writeErr
}
if runUsesSystemDevice {
return E.Errors(writeErr, d.systemDevice.writeBuffers(packetBuffers[runStart:]))
}
return E.Errors(writeErr, d.stackDevice.writeBuffers(packetBuffers[runStart:]))
}
func packetDestination(packet []byte) netip.Addr {
switch header.IPVersion(packet) {
case header.IPv4Version:
return header.IPv4(packet).DestinationAddr()
case header.IPv6Version:
return header.IPv6(packet).DestinationAddr()
default:
return netip.Addr{}
}
}
func (d *systemStackDevice) Close() error {
return E.Errors(d.stackDevice.Close(), d.systemDevice.Close())
}
+214
View File
@@ -0,0 +1,214 @@
package openvpn
import (
"context"
"net/netip"
"sync/atomic"
"time"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network"
)
const (
DefaultMTU = 1500
PacketHeadroom = 4096
)
type PacketWriter func(packetBuffers []*buf.Buffer) error
type Device interface {
N.Dialer
Start() error
UpdateConfiguration(configuration Configuration) error
WriteInboundBuffers(packetBuffers []*buf.Buffer) error
SetPacketWriter(writer PacketWriter)
PortAddresses() (netip.Addr, netip.Addr)
PortMTU() uint32
AttachReturn(returnPath tun.Return) error
DetachReturn(returnPath tun.Return) error
ReturnPath() (tun.Return, int)
Close() error
}
type DeviceOptions struct {
Context context.Context
Logger logger.ContextLogger
System bool
Handler tun.Handler
UDPTimeout time.Duration
ICMPTimeout time.Duration
UDPMapping tun.NATMapping
UDPFiltering tun.NATFiltering
UDPNATMax uint32
InterfaceFinder control.InterfaceFinder
ExcludeInterface []string
Name string
MTU uint32
Configuration Configuration
}
type Configuration struct {
MTU uint32
Address []netip.Prefix
Routes []Route
ExcludedRoutes []Route
DNS []netip.Addr
DNSServers []DNSServer
SearchDomains []string
DNSRoutes []string
Topology string
Interface string
BlockIPv6 bool
}
type Route struct {
Prefix netip.Prefix
Gateway netip.Addr
Metric int
}
type DNSServer struct {
Priority int
Addresses []netip.AddrPort
ResolveDomains []string
DNSSEC string
Transport string
SNI string
}
func NewDevice(options DeviceOptions) (Device, error) {
if !options.System {
return newStackDevice(options)
}
if !tun.WithGVisor {
return newSystemDevice(options)
}
return newSystemStackDevice(options)
}
type baseDevice struct {
packetWriter PacketWriter
returnState atomic.Pointer[returnPathState]
}
func (d *baseDevice) SetPacketWriter(writer PacketWriter) {
d.packetWriter = writer
}
func (d *baseDevice) writeOutbound(packetBuffers []*buf.Buffer) error {
if d.packetWriter == nil {
buf.ReleaseMulti(packetBuffers)
return E.New("missing packet writer")
}
return d.packetWriter(packetBuffers)
}
func (d *baseDevice) processInboundBuffers(packetBuffers []*buf.Buffer, writeBuffers func(packetBuffers []*buf.Buffer) error) error {
if len(packetBuffers) == 0 {
return nil
}
state := d.returnState.Load()
if state == nil {
return writeBuffers(packetBuffers)
}
packets := make([][]byte, len(packetBuffers))
for i, packetBuffer := range packetBuffers {
packetBuffer.ExtendHeader(state.headroom)
packets[i] = packetBuffer.Bytes()
}
unconsumed := state.returnPath.ReturnPackets(packets)
if len(unconsumed) == 0 {
return nil
}
unconsumedBuffers := make([]*buf.Buffer, len(unconsumed))
for i, packet := range unconsumed {
packetBuffer := buf.As(packet)
packetBuffer.Advance(state.headroom)
unconsumedBuffers[i] = packetBuffer
}
return writeBuffers(unconsumedBuffers)
}
func (d *baseDevice) AttachReturn(returnPath tun.Return) error {
headroom := returnPath.ReturnHeadroom()
if headroom > PacketHeadroom {
return E.New("return path headroom ", headroom, " exceeds available ", PacketHeadroom)
}
newState := &returnPathState{
returnPath: returnPath,
headroom: headroom,
}
for {
currentState := d.returnState.Load()
if currentState != nil {
if currentState.returnPath == returnPath {
return nil
}
return E.New("return path already attached")
}
if d.returnState.CompareAndSwap(nil, newState) {
return nil
}
}
}
func (d *baseDevice) DetachReturn(returnPath tun.Return) error {
currentState := d.returnState.Load()
if currentState != nil && currentState.returnPath == returnPath {
d.returnState.CompareAndSwap(currentState, nil)
}
return nil
}
func (d *baseDevice) ReturnPath() (tun.Return, int) {
state := d.returnState.Load()
if state == nil {
return nil, 0
}
return state.returnPath, state.headroom
}
type returnPathState struct {
returnPath tun.Return
headroom int
}
func firstAddresses(addresses []netip.Prefix) (netip.Addr, netip.Addr) {
var inet4Address netip.Addr
var inet6Address netip.Addr
for _, prefix := range addresses {
if prefix.Addr().Is4() && !inet4Address.IsValid() {
inet4Address = prefix.Addr()
} else if prefix.Addr().Is6() && !inet6Address.IsValid() {
inet6Address = prefix.Addr()
}
}
return inet4Address, inet6Address
}
func splitPrefixes(prefixes []netip.Prefix) ([]netip.Prefix, []netip.Prefix) {
var inet4Prefixes []netip.Prefix
var inet6Prefixes []netip.Prefix
for _, prefix := range prefixes {
if prefix.Addr().Is4() {
inet4Prefixes = append(inet4Prefixes, prefix)
} else {
inet6Prefixes = append(inet6Prefixes, prefix)
}
}
return inet4Prefixes, inet6Prefixes
}
func hasRouteOptions(routes []Route) bool {
for _, route := range routes {
if route.Gateway.IsValid() || route.Metric != 0 {
return true
}
}
return false
}
+377
View File
@@ -0,0 +1,377 @@
//go:build with_gvisor
package openvpn
import (
"context"
"net"
"net/netip"
"slices"
"sync"
"sync/atomic"
"github.com/sagernet/gvisor/pkg/buffer"
"github.com/sagernet/gvisor/pkg/tcpip"
"github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet"
"github.com/sagernet/gvisor/pkg/tcpip/header"
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv4"
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv6"
"github.com/sagernet/gvisor/pkg/tcpip/stack"
"github.com/sagernet/gvisor/pkg/tcpip/transport/icmp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
var _ Device = (*stackDevice)(nil)
type stackDevice struct {
baseDevice
stateAccess sync.RWMutex
options DeviceOptions
stack *stack.Stack
endpoint *stackEndpoint
inet4Address netip.Addr
inet6Address netip.Addr
udpForwarder *tun.UDPForwarder
icmpForwarder *tun.ICMPForwarder
logRouteOptions bool
closeOnce sync.Once
}
func newStackDevice(options DeviceOptions) (*stackDevice, error) {
if options.MTU == 0 {
options.MTU = DefaultMTU
}
device := &stackDevice{
options: options,
logRouteOptions: true,
}
endpoint := &stackEndpoint{
device: device,
done: make(chan struct{}),
}
endpoint.mtu.Store(options.MTU)
ipStack, err := tun.NewGVisorStackWithOptions(endpoint, stack.NICOptions{}, true)
if err != nil {
return nil, err
}
device.stack = ipStack
device.endpoint = endpoint
err = device.updateAddresses(nil, options.Configuration.Address)
if err != nil {
ipStack.Close()
return nil, err
}
if options.Handler != nil {
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket)
udpForwarder := tun.NewUDPForwarder(options.Context, ipStack, options.Handler, tun.UDPNatOptions{
Timeout: options.UDPTimeout,
Shared: true,
Mapping: options.UDPMapping,
Filtering: options.UDPFiltering,
MaxSize: options.UDPNATMax,
InterfaceFinder: options.InterfaceFinder,
ExcludeInterface: options.ExcludeInterface,
})
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)
device.udpForwarder = udpForwarder
icmpForwarder := tun.NewICMPForwarder(ipStack, options.Handler, options.Logger)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
device.icmpForwarder = icmpForwarder
}
return device, nil
}
func (d *stackDevice) Start() error {
if d.udpForwarder != nil {
err := d.udpForwarder.Start()
if err != nil {
return err
}
}
return nil
}
func (d *stackDevice) UpdateConfiguration(configuration Configuration) error {
d.stateAccess.Lock()
defer d.stateAccess.Unlock()
if d.logRouteOptions && hasRouteOptions(configuration.Routes) {
d.options.Logger.Debug("route gateway and metric options are not representable by the gVisor stack device; routes are installed by prefix")
d.logRouteOptions = false
}
if configuration.MTU != 0 {
d.options.MTU = configuration.MTU
d.endpoint.mtu.Store(configuration.MTU)
}
previousAddresses := d.options.Configuration.Address
d.options.Configuration = configuration
return d.updateAddresses(previousAddresses, configuration.Address)
}
func (d *stackDevice) updateAddresses(previousAddresses []netip.Prefix, addresses []netip.Prefix) error {
for _, prefix := range previousAddresses {
if slices.Contains(addresses, prefix) {
continue
}
gErr := d.stack.RemoveAddress(tun.DefaultNIC, tun.AddressFromAddr(prefix.Addr()))
if gErr != nil {
return E.New("remove local address ", prefix, ": ", gErr.String())
}
}
for _, prefix := range addresses {
if slices.Contains(previousAddresses, prefix) {
continue
}
protocolAddress := tcpip.ProtocolAddress{
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: tun.AddressFromAddr(prefix.Addr()),
PrefixLen: prefix.Bits(),
},
}
if prefix.Addr().Is4() {
protocolAddress.Protocol = ipv4.ProtocolNumber
} else {
protocolAddress.Protocol = ipv6.ProtocolNumber
}
gErr := d.stack.AddProtocolAddress(tun.DefaultNIC, protocolAddress, stack.AddressProperties{})
if gErr != nil {
return E.New("add local address ", prefix, ": ", gErr.String())
}
}
d.inet4Address, d.inet6Address = firstAddresses(addresses)
return nil
}
func (d *stackDevice) WriteInboundBuffers(packetBuffers []*buf.Buffer) error {
return d.processInboundBuffers(packetBuffers, d.writeBuffers)
}
func (d *stackDevice) writeBuffers(packetBuffers []*buf.Buffer) error {
networkProtocols := make([]tcpip.NetworkProtocolNumber, 0, len(packetBuffers))
stackPacketBuffers := make([]*stack.PacketBuffer, 0, len(packetBuffers))
var packetErr error
for _, packetBuffer := range packetBuffers {
packet := packetBuffer.Bytes()
var networkProtocol tcpip.NetworkProtocolNumber
switch header.IPVersion(packet) {
case header.IPv4Version:
networkProtocol = header.IPv4ProtocolNumber
case header.IPv6Version:
networkProtocol = header.IPv6ProtocolNumber
default:
if packetErr == nil {
packetErr = E.New("invalid IP packet")
}
continue
}
networkProtocols = append(networkProtocols, networkProtocol)
stackPacketBuffers = append(stackPacketBuffers, stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: buffer.MakeWithData(packet),
}))
}
d.endpoint.deliverNetworkPackets(networkProtocols, stackPacketBuffers)
for _, packetBuffer := range stackPacketBuffers {
packetBuffer.DecRef()
}
return packetErr
}
func (d *stackDevice) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if destination.IsIPv6() && d.blockIPv6Enabled() {
return nil, E.New("IPv6 blocked by pushed block-ipv6")
}
inet4Address, inet6Address := d.PortAddresses()
address := tcpip.FullAddress{
NIC: tun.DefaultNIC,
Port: destination.Port,
Addr: tun.AddressFromAddr(destination.Addr),
}
bind := tcpip.FullAddress{
NIC: tun.DefaultNIC,
}
var networkProtocol tcpip.NetworkProtocolNumber
if destination.IsIPv4() {
if !inet4Address.IsValid() {
return nil, E.New("missing IPv4 local address")
}
networkProtocol = header.IPv4ProtocolNumber
bind.Addr = tun.AddressFromAddr(inet4Address)
} else {
if !inet6Address.IsValid() {
return nil, E.New("missing IPv6 local address")
}
networkProtocol = header.IPv6ProtocolNumber
bind.Addr = tun.AddressFromAddr(inet6Address)
}
switch N.NetworkName(network) {
case N.NetworkTCP:
return gonet.DialTCPWithBind(ctx, d.stack, bind, address, networkProtocol)
case N.NetworkUDP:
return gonet.DialUDP(d.stack, &bind, &address, networkProtocol)
default:
return nil, E.Extend(N.ErrUnknownNetwork, network)
}
}
func (d *stackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if destination.IsIPv6() && d.blockIPv6Enabled() {
return nil, E.New("IPv6 blocked by pushed block-ipv6")
}
inet4Address, inet6Address := d.PortAddresses()
bind := tcpip.FullAddress{
NIC: tun.DefaultNIC,
}
var networkProtocol tcpip.NetworkProtocolNumber
if destination.IsIPv4() {
if !inet4Address.IsValid() {
return nil, E.New("missing IPv4 local address")
}
networkProtocol = header.IPv4ProtocolNumber
bind.Addr = tun.AddressFromAddr(inet4Address)
} else {
if !inet6Address.IsValid() {
return nil, E.New("missing IPv6 local address")
}
networkProtocol = header.IPv6ProtocolNumber
bind.Addr = tun.AddressFromAddr(inet6Address)
}
return gonet.DialUDP(d.stack, &bind, nil, networkProtocol)
}
func (d *stackDevice) blockIPv6Enabled() bool {
d.stateAccess.RLock()
defer d.stateAccess.RUnlock()
return d.options.Configuration.BlockIPv6
}
func (d *stackDevice) PortAddresses() (netip.Addr, netip.Addr) {
d.stateAccess.RLock()
defer d.stateAccess.RUnlock()
return d.inet4Address, d.inet6Address
}
func (d *stackDevice) PortMTU() uint32 {
d.stateAccess.RLock()
defer d.stateAccess.RUnlock()
return d.options.MTU
}
func (d *stackDevice) Close() error {
d.closeOnce.Do(func() {
close(d.endpoint.done)
if d.udpForwarder != nil {
d.udpForwarder.Close()
}
if d.icmpForwarder != nil {
d.icmpForwarder.Close()
}
d.stack.Close()
for _, endpoint := range d.stack.CleanupEndpoints() {
endpoint.Abort()
}
d.stack.Wait()
})
return nil
}
type stackEndpoint struct {
device *stackDevice
mtu atomic.Uint32
done chan struct{}
dispatcherAccess sync.RWMutex
dispatcher stack.NetworkDispatcher
}
func (e *stackEndpoint) MTU() uint32 {
return e.mtu.Load()
}
func (e *stackEndpoint) SetMTU(mtu uint32) {
e.mtu.Store(mtu)
}
func (e *stackEndpoint) MaxHeaderLength() uint16 {
return 0
}
func (e *stackEndpoint) LinkAddress() tcpip.LinkAddress {
return ""
}
func (e *stackEndpoint) SetLinkAddress(addr tcpip.LinkAddress) {
}
func (e *stackEndpoint) Capabilities() stack.LinkEndpointCapabilities {
return stack.CapabilityRXChecksumOffload
}
func (e *stackEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
e.dispatcherAccess.Lock()
defer e.dispatcherAccess.Unlock()
e.dispatcher = dispatcher
}
func (e *stackEndpoint) IsAttached() bool {
e.dispatcherAccess.RLock()
defer e.dispatcherAccess.RUnlock()
return e.dispatcher != nil
}
func (e *stackEndpoint) deliverNetworkPackets(networkProtocols []tcpip.NetworkProtocolNumber, packetBuffers []*stack.PacketBuffer) {
e.dispatcherAccess.RLock()
defer e.dispatcherAccess.RUnlock()
if e.dispatcher == nil {
return
}
for i, packetBuffer := range packetBuffers {
e.dispatcher.DeliverNetworkPacket(networkProtocols[i], packetBuffer)
}
}
func (e *stackEndpoint) Wait() {
}
func (e *stackEndpoint) ARPHardwareType() header.ARPHardwareType {
return header.ARPHardwareNone
}
func (e *stackEndpoint) AddHeader(packetBuffer *stack.PacketBuffer) {
}
func (e *stackEndpoint) ParseHeader(packetBuffer *stack.PacketBuffer) bool {
return true
}
func (e *stackEndpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Error) {
packetBuffers := make([]*buf.Buffer, 0, list.Len())
for _, packetBuffer := range list.AsSlice() {
packetSlices := packetBuffer.AsSlices()
packetLength := 0
for _, packetSlice := range packetSlices {
packetLength += len(packetSlice)
}
outboundBuffer := buf.NewSize(PacketHeadroom + packetLength + systemDevicePacketRearSpace)
outboundBuffer.Resize(PacketHeadroom, 0)
for _, packetSlice := range packetSlices {
_, _ = outboundBuffer.Write(packetSlice)
}
packetBuffers = append(packetBuffers, outboundBuffer)
}
err := e.device.writeOutbound(packetBuffers)
if err != nil {
return 0, &tcpip.ErrClosedForSend{}
}
return list.Len(), nil
}
func (e *stackEndpoint) Close() {
}
func (e *stackEndpoint) SetOnCloseAction(action func()) {
}
+13
View File
@@ -0,0 +1,13 @@
//go:build !with_gvisor
package openvpn
import E "github.com/sagernet/sing/common/exceptions"
func newStackDevice(options DeviceOptions) (Device, error) {
return nil, E.New("system:false requires the with_gvisor build tag")
}
func newSystemStackDevice(options DeviceOptions) (Device, error) {
return nil, E.New("system stack requires the with_gvisor build tag")
}
+382
View File
@@ -0,0 +1,382 @@
package openvpn
import (
"context"
"net"
"net/netip"
"slices"
"sync"
"syscall"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/gtcpip/header"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
)
var _ Device = (*systemDevice)(nil)
const (
systemDeviceReadBufferSize = 65535 + tun.PacketOffset
systemDevicePacketRearSpace = 64
)
type systemDevice struct {
baseDevice
stateAccess sync.RWMutex
options DeviceOptions
dialer N.Dialer
device tun.Tun
inet4Address netip.Addr
inet6Address netip.Addr
closed bool
}
func newSystemDevice(options DeviceOptions) (*systemDevice, error) {
if options.Name == "" {
options.Name = tun.CalculateInterfaceName("ovpn")
}
if options.MTU == 0 {
options.MTU = DefaultMTU
}
interfaceDialer, err := dialer.NewDefault(options.Context, option.DialerOptions{
AbstractDialerOptions: option.AbstractDialerOptions{
BindInterface: options.Name,
},
})
if err != nil {
return nil, err
}
inet4Address, inet6Address := firstAddresses(options.Configuration.Address)
return &systemDevice{
options: options,
dialer: interfaceDialer,
inet4Address: inet4Address,
inet6Address: inet6Address,
}, nil
}
func (d *systemDevice) Start() error {
d.stateAccess.Lock()
defer d.stateAccess.Unlock()
return d.startLocked()
}
func (d *systemDevice) startLocked() error {
if d.closed {
return net.ErrClosed
}
if d.device != nil {
return nil
}
tunOptions := d.buildTunOptions()
tunInterface, err := tun.New(tunOptions)
if err != nil {
return err
}
err = tunInterface.Start()
if err != nil {
tunInterface.Close()
return err
}
d.device = tunInterface
d.options.Logger.Info("started at ", d.options.Name)
go d.readLoop(tunInterface, int(d.options.MTU))
return nil
}
func (d *systemDevice) buildTunOptions() tun.Options {
inet4Address, inet6Address := firstAddresses(d.options.Configuration.Address)
d.inet4Address = inet4Address
d.inet6Address = inet6Address
inet4Addresses, inet6Addresses := splitPrefixes(d.options.Configuration.Address)
networkManager := service.FromContext[adapter.NetworkManager](d.options.Context)
tunOptions := tun.Options{
Name: d.options.Name,
Inet4Address: inet4Addresses,
Inet6Address: inet6Addresses,
MTU: d.options.MTU,
GSO: true,
InterfaceScope: true,
DNSMode: tun.DNSModeDisabled,
InterfaceMonitor: nil,
InterfaceFinder: nil,
Logger: d.options.Logger,
IPRoute2TableIndex: tun.DefaultIPRoute2TableIndex,
IPRoute2RuleIndex: tun.DefaultIPRoute2RuleIndex,
EXP_DisableDNSHijack: true,
}
if networkManager != nil {
tunOptions.InterfaceMonitor = networkManager.InterfaceMonitor()
tunOptions.InterfaceFinder = networkManager.InterfaceFinder()
}
return tunOptions
}
func (d *systemDevice) readLoop(tunInterface tun.Tun, mtu int) {
linuxTUN, isLinuxTUN := tunInterface.(tun.LinuxTUN)
if isLinuxTUN && linuxTUN.BatchSize() > 1 {
d.readLoopLinux(linuxTUN, linuxTUN.BatchSize(), mtu)
return
}
darwinTUN, isDarwinTUN := tunInterface.(tun.DarwinTUN)
if isDarwinTUN {
d.readLoopDarwin(darwinTUN)
return
}
packetBuffer := buf.NewSize(PacketHeadroom + systemDeviceReadBufferSize + systemDevicePacketRearSpace)
defer packetBuffer.Release()
for {
packetBuffer.Reset()
packetBuffer.Resize(PacketHeadroom, 0)
readN, err := tunInterface.Read(packetBuffer.FreeBytes()[:systemDeviceReadBufferSize])
if err != nil {
if E.IsClosed(err) {
return
}
d.options.Logger.Error(E.Cause(err, "read packet"))
return
}
if readN <= tun.PacketOffset {
continue
}
packetBuffer.Truncate(readN)
packetBuffer.Advance(tun.PacketOffset)
if d.blockIPv6Enabled() && header.IPVersion(packetBuffer.Bytes()) == header.IPv6Version {
continue
}
packetBuffer.IncRef()
err = d.writeOutbound([]*buf.Buffer{packetBuffer})
packetBuffer.DecRef()
if err != nil {
d.options.Logger.Error(E.Cause(err, "write packet"))
return
}
}
}
func (d *systemDevice) readLoopLinux(tunInterface tun.LinuxTUN, batchSize int, mtu int) {
packetBuffers := make([]*buf.Buffer, batchSize)
readBuffers := make([][]byte, batchSize)
packetSizes := make([]int, batchSize)
outboundBuffers := make([]*buf.Buffer, 0, batchSize)
for i := range packetBuffers {
packetBuffers[i] = buf.NewSize(PacketHeadroom + mtu + systemDevicePacketRearSpace)
}
defer buf.ReleaseMulti(packetBuffers)
for {
for i, packetBuffer := range packetBuffers {
packetBuffer.Reset()
packetBuffer.Resize(PacketHeadroom, 0)
readBuffers[i] = packetBuffer.FreeBytes()[:mtu]
}
packetCount, readErr := tunInterface.BatchRead(readBuffers, 0, packetSizes)
outboundBuffers = outboundBuffers[:0]
blockIPv6 := d.blockIPv6Enabled()
for i := range packetCount {
packetBuffers[i].Truncate(packetSizes[i])
if blockIPv6 && header.IPVersion(packetBuffers[i].Bytes()) == header.IPv6Version {
continue
}
packetBuffers[i].IncRef()
outboundBuffers = append(outboundBuffers, packetBuffers[i])
}
if len(outboundBuffers) > 0 {
writeErr := d.writeOutbound(outboundBuffers)
for _, packetBuffer := range outboundBuffers {
packetBuffer.DecRef()
}
if writeErr != nil {
d.options.Logger.Error(E.Cause(writeErr, "write packet batch"))
return
}
}
if readErr != nil {
if E.IsClosed(readErr) {
return
}
d.options.Logger.Error(E.Cause(readErr, "batch read packet"))
return
}
}
}
func (d *systemDevice) readLoopDarwin(tunInterface tun.DarwinTUN) {
for {
packetBuffers, readErr := tunInterface.BatchRead()
outboundBuffers := packetBuffers[:0]
blockIPv6 := d.blockIPv6Enabled()
for _, packetBuffer := range packetBuffers {
if packetBuffer.IsEmpty() {
packetBuffer.Release()
continue
}
if blockIPv6 && header.IPVersion(packetBuffer.Bytes()) == header.IPv6Version {
packetBuffer.Release()
continue
}
outboundBuffers = append(outboundBuffers, packetBuffer)
}
if len(outboundBuffers) > 0 {
writeErr := d.writeOutbound(outboundBuffers)
if writeErr != nil {
d.options.Logger.Error(E.Cause(writeErr, "write packet batch"))
return
}
}
if readErr != nil {
if E.IsClosed(readErr) || E.IsMulti(readErr, syscall.EBADF) {
return
}
d.options.Logger.Error(E.Cause(readErr, "batch read packet"))
return
}
}
}
func (d *systemDevice) UpdateConfiguration(configuration Configuration) error {
d.stateAccess.Lock()
defer d.stateAccess.Unlock()
previousConfiguration := d.options.Configuration
previousMTU := d.options.MTU
updatedMTU := d.options.MTU
if configuration.MTU != 0 {
updatedMTU = configuration.MTU
}
d.options.MTU = updatedMTU
d.options.Configuration = configuration
if d.device == nil {
inet4Address, inet6Address := firstAddresses(configuration.Address)
d.inet4Address = inet4Address
d.inet6Address = inet6Address
return nil
}
if !slices.Equal(previousConfiguration.Address, configuration.Address) ||
previousMTU != updatedMTU {
d.device.Close()
d.device = nil
return d.startLocked()
}
return nil
}
func (d *systemDevice) blockIPv6Enabled() bool {
d.stateAccess.RLock()
defer d.stateAccess.RUnlock()
return d.options.Configuration.BlockIPv6
}
func (d *systemDevice) WriteInboundBuffers(packetBuffers []*buf.Buffer) error {
return d.processInboundBuffers(packetBuffers, d.writeBuffers)
}
func (d *systemDevice) writeBuffers(packetBuffers []*buf.Buffer) error {
d.stateAccess.RLock()
tunInterface := d.device
d.stateAccess.RUnlock()
if tunInterface == nil {
return E.New("system device is not ready")
}
linuxTUN, isLinuxTUN := tunInterface.(tun.LinuxTUN)
if isLinuxTUN {
headroom := linuxTUN.FrontHeadroom()
packets := make([][]byte, len(packetBuffers))
var temporaryBuffers []*buf.Buffer
for i, packetBuffer := range packetBuffers {
if packetBuffer.Start() >= headroom {
packetBuffer.ExtendHeader(headroom)
packets[i] = packetBuffer.Bytes()
packetBuffer.Advance(headroom)
continue
}
temporaryBuffer := buf.NewSize(headroom + packetBuffer.Len())
temporaryBuffer.Resize(headroom, 0)
_, _ = temporaryBuffer.Write(packetBuffer.Bytes())
temporaryBuffer.ExtendHeader(headroom)
packets[i] = temporaryBuffer.Bytes()
temporaryBuffers = append(temporaryBuffers, temporaryBuffer)
}
_, err := linuxTUN.BatchWrite(packets, headroom)
buf.ReleaseMulti(temporaryBuffers)
return err
}
darwinTUN, isDarwinTUN := tunInterface.(tun.DarwinTUN)
if isDarwinTUN {
return darwinTUN.BatchWrite(packetBuffers)
}
for _, packetBuffer := range packetBuffers {
err := d.writePacket(packetBuffer.Bytes())
if err != nil {
return err
}
}
return nil
}
func (d *systemDevice) writePacket(packet []byte) error {
d.stateAccess.RLock()
tunInterface := d.device
d.stateAccess.RUnlock()
if tunInterface == nil {
return E.New("system device is not ready")
}
if tun.PacketOffset == 0 {
_, err := tunInterface.Write(packet)
return err
}
writeBuffer := make([]byte, tun.PacketOffset+len(packet))
tun.PacketFillHeader(writeBuffer[:tun.PacketOffset], header.IPVersion(packet))
copy(writeBuffer[tun.PacketOffset:], packet)
_, err := tunInterface.Write(writeBuffer)
return err
}
func (d *systemDevice) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if !destination.Addr.IsValid() {
return nil, E.New("invalid non-IP destination")
}
return d.dialer.DialContext(ctx, network, destination)
}
func (d *systemDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if !destination.Addr.IsValid() {
return nil, E.New("invalid non-IP destination")
}
return d.dialer.ListenPacket(ctx, destination)
}
func (d *systemDevice) PortAddresses() (netip.Addr, netip.Addr) {
d.stateAccess.RLock()
defer d.stateAccess.RUnlock()
return d.inet4Address, d.inet6Address
}
func (d *systemDevice) PortMTU() uint32 {
d.stateAccess.RLock()
defer d.stateAccess.RUnlock()
return d.options.MTU
}
func (d *systemDevice) Close() error {
d.stateAccess.Lock()
defer d.stateAccess.Unlock()
d.closed = true
if d.device == nil {
return nil
}
err := d.device.Close()
d.device = nil
return err
}
func (d *systemDevice) configurationAddresses() []netip.Prefix {
d.stateAccess.RLock()
defer d.stateAccess.RUnlock()
return slices.Clone(d.options.Configuration.Address)
}
+117
View File
@@ -0,0 +1,117 @@
//go:build with_gvisor
package openvpn
import (
"net/netip"
"github.com/sagernet/sing-tun/gtcpip/header"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
)
var _ Device = (*systemStackDevice)(nil)
type systemStackDevice struct {
*systemDevice
stackDevice *stackDevice
}
func newSystemStackDevice(options DeviceOptions) (*systemStackDevice, error) {
system, err := newSystemDevice(options)
if err != nil {
return nil, err
}
stackOptions := options
stackOptions.System = false
stackOptions.Name = system.options.Name
stackOptions.ExcludeInterface = []string{system.options.Name}
stackDevice, err := newStackDevice(stackOptions)
if err != nil {
system.Close()
return nil, err
}
stackDevice.logRouteOptions = false
return &systemStackDevice{
systemDevice: system,
stackDevice: stackDevice,
}, nil
}
func (d *systemStackDevice) SetPacketWriter(writer PacketWriter) {
d.systemDevice.SetPacketWriter(writer)
d.stackDevice.SetPacketWriter(writer)
}
func (d *systemStackDevice) Start() error {
err := d.stackDevice.Start()
if err != nil {
return err
}
err = d.systemDevice.Start()
return err
}
func (d *systemStackDevice) UpdateConfiguration(configuration Configuration) error {
err := d.systemDevice.UpdateConfiguration(configuration)
if err != nil {
return err
}
return d.stackDevice.UpdateConfiguration(configuration)
}
func (d *systemStackDevice) WriteInboundBuffers(packetBuffers []*buf.Buffer) error {
return d.systemDevice.processInboundBuffers(packetBuffers, d.writeBuffers)
}
func (d *systemStackDevice) writeBuffers(packetBuffers []*buf.Buffer) error {
addresses := d.systemDevice.configurationAddresses()
runStart := 0
runUsesSystemDevice := false
var writeErr error
for i, packetBuffer := range packetBuffers {
destination := packetDestination(packetBuffer.Bytes())
useSystemDevice := false
for _, prefix := range addresses {
if prefix.Contains(destination) {
useSystemDevice = true
break
}
}
if i > runStart && useSystemDevice != runUsesSystemDevice {
var err error
if runUsesSystemDevice {
err = d.systemDevice.writeBuffers(packetBuffers[runStart:i])
} else {
err = d.stackDevice.writeBuffers(packetBuffers[runStart:i])
}
writeErr = E.Errors(writeErr, err)
runStart = i
}
if i == runStart {
runUsesSystemDevice = useSystemDevice
}
}
if runStart == len(packetBuffers) {
return writeErr
}
if runUsesSystemDevice {
return E.Errors(writeErr, d.systemDevice.writeBuffers(packetBuffers[runStart:]))
}
return E.Errors(writeErr, d.stackDevice.writeBuffers(packetBuffers[runStart:]))
}
func packetDestination(packet []byte) netip.Addr {
switch header.IPVersion(packet) {
case header.IPv4Version:
return header.IPv4(packet).DestinationAddr()
case header.IPv6Version:
return header.IPv6(packet).DestinationAddr()
default:
return netip.Addr{}
}
}
func (d *systemStackDevice) Close() error {
return E.Errors(d.stackDevice.Close(), d.systemDevice.Close())
}
+2 -3
View File
@@ -17,11 +17,10 @@ import (
"github.com/sagernet/sing-box/common/congestion"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
"github.com/sagernet/sing/common/logger"
"github.com/sagernet/quic-go"
"github.com/sagernet/quic-go/http3"
@@ -101,7 +100,7 @@ func NewClient(ctx context.Context, options ClientOptions) (*Client, error) {
if err != nil {
return nil, err
}
conn, err := qtls.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), options.TLSConfig, cfg)
conn, err := qtls.DialEarly(ctx, udpConn, options.TLSConfig, cfg)
if err != nil {
return nil, err
}
+3
View File
@@ -40,6 +40,9 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
}
dialOptions = append(dialOptions, grpc.WithTransportCredentials(NewTLSTransportCredentials(tlsConfig)))
if tlsConfig.ServerName() != "" {
dialOptions = append(dialOptions, grpc.WithAuthority(tlsConfig.ServerName()))
}
} else {
dialOptions = append(dialOptions, grpc.WithTransportCredentials(insecure.NewCredentials()))
}
-2
View File
@@ -22,8 +22,6 @@ func NewTLSTransportCredentials(config tls.Config) credentials.TransportCredenti
func (c *TLSTransportCredentials) Info() credentials.ProtocolInfo {
return credentials.ProtocolInfo{
SecurityProtocol: "tls",
SecurityVersion: "1.2",
ServerName: c.config.ServerName(),
}
}
+2 -1
View File
@@ -22,7 +22,7 @@ import (
sHttp "github.com/sagernet/sing/protocol/http"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"golang.org/x/net/http2/h2c" //nolint:staticcheck
)
var _ adapter.V2RayServerTransport = (*Server)(nil)
@@ -56,6 +56,7 @@ func NewServer(ctx context.Context, logger logger.ContextLogger, options option.
return log.ContextWithNewID(ctx)
},
}
//nolint:staticcheck
server.h2cHandler = h2c.NewHandler(server, server.h2Server)
return server, nil
}
+1 -19
View File
@@ -3,7 +3,6 @@ package v2rayhttp
import (
"net/http"
"reflect"
"sync"
"unsafe"
E "github.com/sagernet/sing/common/exceptions"
@@ -11,12 +10,6 @@ import (
"golang.org/x/net/http2"
)
type clientConnPool struct {
t *http2.Transport
mu sync.Mutex
conns map[string][]*http2.ClientConn // key is host:port
}
type efaceWords struct {
typ unsafe.Pointer
data unsafe.Pointer
@@ -28,20 +21,9 @@ func ResetTransport(rawTransport http.RoundTripper) http.RoundTripper {
transport.CloseIdleConnections()
return transport.Clone()
case *http2.Transport:
connPool := transportConnPool(transport)
p := (*clientConnPool)((*efaceWords)(unsafe.Pointer(&connPool)).data)
p.mu.Lock()
defer p.mu.Unlock()
for _, vv := range p.conns {
for _, cc := range vv {
cc.Close()
}
}
closeHTTP2Connections(transport)
return transport
default:
panic(E.New("unknown transport type: ", reflect.TypeOf(transport)))
}
}
//go:linkname transportConnPool golang.org/x/net/http2.(*Transport).connPool
func transportConnPool(t *http2.Transport) http2.ClientConnPool
+55
View File
@@ -0,0 +1,55 @@
//go:build go1.27 && badlinkname
package v2rayhttp
import (
"net/http"
"sync"
"unsafe"
"golang.org/x/net/http2"
)
// cmd/compile creates the method symbols reachable from *http.Transport's field types with
// their package recorded when the type is first used by a declaration; a linkname pull
// processed afterwards reuses that symbol as a package-indexed reference, which the linker's
// -checklinkname does not inspect. This declaration must precede the linkname declarations.
var _ *http.Transport
// net/http/internal/http2.Transport
type internalTransport struct {
t1 [2]uintptr // TransportConfig
connPool *clientConnPool
}
// net/http/internal/http2.clientConnPool
type clientConnPool struct {
t *internalTransport
mu sync.Mutex
conns map[string][]unsafe.Pointer // key is host:port, value is []*ClientConn
}
func closeHTTP2Connections(transport *http2.Transport) {
h2Transport := transportFromH1Transport(transportInit(transport))
t := (*internalTransport)((*efaceWords)(unsafe.Pointer(&h2Transport)).data)
if t == nil {
return
}
p := t.connPool
p.mu.Lock()
defer p.mu.Unlock()
for _, vv := range p.conns {
for _, cc := range vv {
clientConnClose(cc)
}
}
}
//go:linkname transportInit golang.org/x/net/http2.(*Transport).init
func transportInit(t *http2.Transport) *http.Transport
//go:linkname transportFromH1Transport net/http/internal/http2_test.transportFromH1Transport
func transportFromH1Transport(t *http.Transport) any
//go:linkname clientConnClose net/http/internal/http2.(*ClientConn).Close
func clientConnClose(cc unsafe.Pointer) error
@@ -0,0 +1,9 @@
//go:build go1.27 && !badlinkname
package v2rayhttp
import "golang.org/x/net/http2"
func closeHTTP2Connections(transport *http2.Transport) {
transport.CloseIdleConnections()
}
+31
View File
@@ -0,0 +1,31 @@
//go:build !go1.27
package v2rayhttp
import (
"sync"
"unsafe"
"golang.org/x/net/http2"
)
type clientConnPool struct {
t *http2.Transport
mu sync.Mutex
conns map[string][]*http2.ClientConn // key is host:port
}
func closeHTTP2Connections(transport *http2.Transport) {
connPool := transportConnPool(transport)
p := (*clientConnPool)((*efaceWords)(unsafe.Pointer(&connPool)).data)
p.mu.Lock()
defer p.mu.Unlock()
for _, vv := range p.conns {
for _, cc := range vv {
cc.Close()
}
}
}
//go:linkname transportConnPool golang.org/x/net/http2.(*Transport).connPool
func transportConnPool(t *http2.Transport) http2.ClientConnPool
+2 -1
View File
@@ -24,7 +24,7 @@ import (
sHttp "github.com/sagernet/sing/protocol/http"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"golang.org/x/net/http2/h2c" //nolint:staticcheck
)
var _ adapter.V2RayServerTransport = (*Server)(nil)
@@ -71,6 +71,7 @@ func NewServer(ctx context.Context, logger logger.ContextLogger, options option.
return log.ContextWithNewID(ctx)
},
}
//nolint:staticcheck
server.h2cHandler = h2c.NewHandler(server, server.h2Server)
return server, nil
}
+4 -6
View File
@@ -15,7 +15,6 @@ import (
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-quic"
"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"
)
@@ -72,17 +71,16 @@ func (c *Client) offerNew() (*quic.Conn, error) {
if err != nil {
return nil, err
}
packetConn := bufio.NewUnbindPacketConn(udpConn)
quicConn, err := qtls.Dial(c.ctx, packetConn, udpConn.RemoteAddr(), c.tlsConfig, c.quicConfig)
quicConn, err := qtls.Dial(c.ctx, udpConn, c.tlsConfig, c.quicConfig)
if err != nil {
packetConn.Close()
udpConn.Close()
return nil, err
}
// quic-go does not take ownership of the packet conn passed to Dial:
// quic-go does not take ownership of the conn passed to Dial:
// when the connection ends it only stops reading.
go func() {
<-quicConn.Context().Done()
packetConn.Close()
udpConn.Close()
}()
c.conn.Store(quicConn)
c.rawConn = udpConn
+12 -13
View File
@@ -73,11 +73,22 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
}, nil
}
func (c *Client) dialContext(ctx context.Context, requestURL *url.URL, headers http.Header) (*WebsocketConn, error) {
func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
conn, err := c.dialer.DialContext(ctx, N.NetworkTCP, c.serverAddr)
if err != nil {
return nil, err
}
if c.maxEarlyData > 0 {
return &EarlyWebsocketConn{Client: c, rawConn: conn, create: make(chan struct{})}, nil
}
websocketConn, err := c.upgrade(conn, &c.requestURL, c.headers)
if err != nil {
return nil, err
}
return websocketConn, nil
}
func (c *Client) upgrade(conn net.Conn, requestURL *url.URL, headers http.Header) (*WebsocketConn, error) {
var deadlineConn net.Conn
if deadline.NeedAdditionalReadDeadline(conn) {
deadlineConn = deadline.NewConn(conn)
@@ -108,18 +119,6 @@ func (c *Client) dialContext(ctx context.Context, requestURL *url.URL, headers h
return NewConn(conn, nil, ws.StateClientSide), nil
}
func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
if c.maxEarlyData <= 0 {
conn, err := c.dialContext(ctx, &c.requestURL, c.headers)
if err != nil {
return nil, err
}
return conn, nil
} else {
return &EarlyWebsocketConn{Client: c, ctx: ctx, create: make(chan struct{})}, nil
}
}
func (c *Client) Close() error {
return nil
}
+20 -22
View File
@@ -1,7 +1,6 @@
package v2raywebsocket
import (
"context"
"encoding/base64"
"errors"
"io"
@@ -16,7 +15,6 @@ import (
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/ws"
"github.com/sagernet/ws/wsutil"
)
@@ -135,11 +133,11 @@ func (c *WebsocketConn) Upstream() any {
type EarlyWebsocketConn struct {
*Client
ctx context.Context
conn atomic.Pointer[WebsocketConn]
access sync.Mutex
create chan struct{}
err error
rawConn net.Conn
conn atomic.Pointer[WebsocketConn]
access sync.Mutex
create chan struct{}
err error
}
func (c *EarlyWebsocketConn) Read(b []byte) (n int, err error) {
@@ -172,14 +170,14 @@ func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
if c.earlyDataHeaderName == "" {
requestURL := c.requestURL
requestURL.Path += earlyDataString
conn, err = c.dialContext(c.ctx, &requestURL, c.headers)
conn, err = c.upgrade(c.rawConn, &requestURL, c.headers)
} else {
headers := c.headers.Clone()
headers.Set(c.earlyDataHeaderName, earlyDataString)
conn, err = c.dialContext(c.ctx, &c.requestURL, headers)
conn, err = c.upgrade(c.rawConn, &c.requestURL, headers)
}
} else {
conn, err = c.dialContext(c.ctx, &c.requestURL, c.headers)
conn, err = c.upgrade(c.rawConn, &c.requestURL, c.headers)
}
if err != nil {
return err
@@ -240,26 +238,26 @@ func (c *EarlyWebsocketConn) WriteBuffer(buffer *buf.Buffer) error {
func (c *EarlyWebsocketConn) Close() error {
conn := c.conn.Load()
if conn == nil {
if conn != nil {
return conn.Close()
}
c.rawConn.Close()
c.access.Lock()
defer c.access.Unlock()
if c.conn.Load() != nil || c.err != nil {
return nil
}
return conn.Close()
c.err = net.ErrClosed
close(c.create)
return nil
}
func (c *EarlyWebsocketConn) LocalAddr() net.Addr {
conn := c.conn.Load()
if conn == nil {
return M.Socksaddr{}
}
return conn.LocalAddr()
return c.rawConn.LocalAddr()
}
func (c *EarlyWebsocketConn) RemoteAddr() net.Addr {
conn := c.conn.Load()
if conn == nil {
return M.Socksaddr{}
}
return conn.RemoteAddr()
return c.rawConn.RemoteAddr()
}
func (c *EarlyWebsocketConn) SetDeadline(t time.Time) error {
+1 -2
View File
@@ -26,7 +26,6 @@ import (
"github.com/sagernet/sing-box/option"
qtls "github.com/sagernet/sing-quic"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -346,7 +345,7 @@ func createHTTPClient(ctx context.Context, dest M.Socksaddr, dialer N.Dialer, op
if dErr != nil {
return nil, dErr
}
conn, dErr := qtls.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), tlsConfig, cfg)
conn, dErr := qtls.DialEarly(ctx, udpConn, tlsConfig, cfg)
if dErr != nil {
_ = udpConn.Close()
return nil, dErr
+17 -17
View File
@@ -5,8 +5,8 @@ import (
"net/netip"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
"github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/wireguard-go/device"
@@ -23,17 +23,22 @@ type Device interface {
}
type DeviceOptions struct {
Context context.Context
Logger logger.ContextLogger
System bool
Handler tun.Handler
UDPTimeout time.Duration
ICMPTimeout time.Duration
CreateDialer func(interfaceName string) N.Dialer
Name string
MTU uint32
Address []netip.Prefix
AllowedAddress []netip.Prefix
Context context.Context
Logger logger.ContextLogger
System bool
Handler tun.Handler
UDPTimeout time.Duration
ICMPTimeout time.Duration
UDPMapping tun.NATMapping
UDPFiltering tun.NATFiltering
UDPNATMax uint32
NetworkMonitor tun.NetworkUpdateMonitor
InterfaceFinder control.InterfaceFinder
CreateDialer func(interfaceName string) N.Dialer
Name string
MTU uint32
Address []netip.Prefix
AllowedAddress []netip.Prefix
}
func NewDevice(options DeviceOptions) (Device, error) {
@@ -45,8 +50,3 @@ func NewDevice(options DeviceOptions) (Device, error) {
return newSystemStackDevice(options)
}
}
type NatDevice interface {
Device
CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
}
-103
View File
@@ -1,103 +0,0 @@
package wireguard
import (
"context"
"sync/atomic"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/ping"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/logger"
)
var _ Device = (*natDeviceWrapper)(nil)
type natDeviceWrapper struct {
Device
ctx context.Context
logger logger.ContextLogger
packetOutbound chan *buf.Buffer
rewriter *ping.SourceRewriter
buffer [][]byte
}
func NewNATDevice(ctx context.Context, logger logger.ContextLogger, upstream Device) NatDevice {
wrapper := &natDeviceWrapper{
Device: upstream,
ctx: ctx,
logger: logger,
packetOutbound: make(chan *buf.Buffer, 256),
rewriter: ping.NewSourceRewriter(ctx, logger, upstream.Inet4Address(), upstream.Inet6Address()),
}
return wrapper
}
func (d *natDeviceWrapper) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) {
select {
case packet := <-d.packetOutbound:
defer packet.Release()
sizes[0] = copy(bufs[0][offset:], packet.Bytes())
return 1, nil
default:
}
return d.Device.Read(bufs, sizes, offset)
}
func (d *natDeviceWrapper) Write(bufs [][]byte, offset int) (int, error) {
for _, buffer := range bufs {
handled, err := d.rewriter.WriteBack(buffer[offset:])
if handled {
if err != nil {
return 0, err
}
} else {
d.buffer = append(d.buffer, buffer)
}
}
if len(d.buffer) > 0 {
_, err := d.Device.Write(d.buffer, offset)
if err != nil {
return 0, err
}
d.buffer = d.buffer[:0]
}
return 0, nil
}
func (d *natDeviceWrapper) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
ctx := log.ContextWithNewID(d.ctx)
session := tun.DirectRouteSession{
Source: metadata.Source.Addr,
Destination: metadata.Destination.Addr,
}
d.rewriter.CreateSession(session, routeContext)
d.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
return &natDestination{device: d, session: session}, nil
}
var _ tun.DirectRouteDestination = (*natDestination)(nil)
type natDestination struct {
device *natDeviceWrapper
session tun.DirectRouteSession
closed atomic.Bool
}
func (d *natDestination) WritePacket(buffer *buf.Buffer) error {
d.device.rewriter.RewritePacket(buffer.Bytes())
d.device.packetOutbound <- buffer
return nil
}
func (d *natDestination) Close() error {
d.closed.Store(true)
d.device.rewriter.DeleteSession(d.session)
return nil
}
func (d *natDestination) IsClosed() bool {
return d.closed.Load()
}
+29 -37
View File
@@ -8,7 +8,6 @@ import (
"net/netip"
"os"
"sync"
"time"
"github.com/sagernet/gvisor/pkg/buffer"
"github.com/sagernet/gvisor/pkg/tcpip"
@@ -20,10 +19,7 @@ import (
"github.com/sagernet/gvisor/pkg/tcpip/transport/icmp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/ping"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
@@ -32,11 +28,9 @@ import (
wgTun "github.com/sagernet/wireguard-go/tun"
)
var _ NatDevice = (*stackDevice)(nil)
var _ Device = (*stackDevice)(nil)
type stackDevice struct {
ctx context.Context
logger log.ContextLogger
stack *stack.Stack
mtu uint32
events chan wgTun.Event
@@ -47,12 +41,12 @@ type stackDevice struct {
dispatcher stack.NetworkDispatcher
inet4Address netip.Addr
inet6Address netip.Addr
icmpForwarder *tun.ICMPForwarder
udpForwarder *tun.UDPForwarder
}
func newStackDevice(options DeviceOptions) (*stackDevice, error) {
tunDevice := &stackDevice{
ctx: options.Context,
logger: options.Logger,
mtu: options.MTU,
events: make(chan wgTun.Event, 1),
outbound: make(chan *stack.PacketBuffer, 256),
@@ -63,10 +57,6 @@ func newStackDevice(options DeviceOptions) (*stackDevice, error) {
if err != nil {
return nil, err
}
var (
inet4Address netip.Addr
inet6Address netip.Addr
)
for _, prefix := range options.Address {
addr := tun.AddressFromAddr(prefix.Addr())
protoAddr := tcpip.ProtocolAddress{
@@ -76,12 +66,10 @@ func newStackDevice(options DeviceOptions) (*stackDevice, error) {
},
}
if prefix.Addr().Is4() {
inet4Address = prefix.Addr()
tunDevice.inet4Address = inet4Address
tunDevice.inet4Address = prefix.Addr()
protoAddr.Protocol = ipv4.ProtocolNumber
} else {
inet6Address = prefix.Addr()
tunDevice.inet6Address = inet6Address
tunDevice.inet6Address = prefix.Addr()
protoAddr.Protocol = ipv6.ProtocolNumber
}
gErr := ipStack.AddProtocolAddress(tun.DefaultNIC, protoAddr, stack.AddressProperties{})
@@ -92,11 +80,20 @@ func newStackDevice(options DeviceOptions) (*stackDevice, error) {
tunDevice.stack = ipStack
if options.Handler != nil {
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket)
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket)
icmpForwarder := tun.NewICMPForwarder(options.Context, ipStack, options.Handler, options.ICMPTimeout)
icmpForwarder.SetLocalAddresses(inet4Address, inet6Address)
udpForwarder := tun.NewUDPForwarder(options.Context, ipStack, options.Handler, tun.UDPNatOptions{
Timeout: options.UDPTimeout,
Shared: true,
Mapping: options.UDPMapping,
Filtering: options.UDPFiltering,
MaxSize: options.UDPNATMax,
InterfaceFinder: options.InterfaceFinder,
})
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)
tunDevice.udpForwarder = udpForwarder
icmpForwarder := tun.NewICMPForwarder(ipStack, options.Handler, options.Logger)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
tunDevice.icmpForwarder = icmpForwarder
}
return tunDevice, nil
}
@@ -179,6 +176,12 @@ func (w *stackDevice) SetDevice(device *device.Device) {
}
func (w *stackDevice) Start() error {
if w.udpForwarder != nil {
err := w.udpForwarder.Start()
if err != nil {
return err
}
}
w.events <- wgTun.EventUp
return nil
}
@@ -255,6 +258,12 @@ func (w *stackDevice) Close() error {
w.closeOnce.Do(func() {
close(w.done)
close(w.events)
if w.icmpForwarder != nil {
w.icmpForwarder.Close()
}
if w.udpForwarder != nil {
_ = w.udpForwarder.Close()
}
w.stack.Close()
for _, endpoint := range w.stack.CleanupEndpoints() {
endpoint.Abort()
@@ -268,23 +277,6 @@ func (w *stackDevice) BatchSize() int {
return 1
}
func (w *stackDevice) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
ctx := log.ContextWithNewID(w.ctx)
destination, err := ping.ConnectGVisor(
ctx, w.logger,
metadata.Source.Addr, metadata.Destination.Addr,
routeContext,
w.stack,
w.inet4Address, w.inet6Address,
timeout,
)
if err != nil {
return nil, err
}
w.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
return destination, nil
}
var _ stack.LinkEndpoint = (*wireEndpoint)(nil)
type wireEndpoint stackDevice
+1
View File
@@ -93,6 +93,7 @@ func (w *systemDevice) Start() error {
MTU: w.options.MTU,
GSO: true,
InterfaceScope: true,
DNSMode: tun.DNSModeDisabled,
Inet4RouteAddress: common.Filter(w.options.AllowedAddress, func(it netip.Prefix) bool {
return it.Addr().Is4()
}),
+47 -47
View File
@@ -3,10 +3,8 @@
package wireguard
import (
"context"
"net/netip"
"sync"
"time"
"github.com/sagernet/gvisor/pkg/buffer"
"github.com/sagernet/gvisor/pkg/tcpip"
@@ -17,12 +15,8 @@ import (
"github.com/sagernet/gvisor/pkg/tcpip/transport/icmp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/ping"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
"github.com/sagernet/wireguard-go/device"
)
@@ -30,12 +24,12 @@ var _ Device = (*systemStackDevice)(nil)
type systemStackDevice struct {
*systemDevice
ctx context.Context
logger logger.ContextLogger
stack *stack.Stack
endpoint *deviceEndpoint
writeBufs [][]byte
closeOnce sync.Once
stack *stack.Stack
endpoint *deviceEndpoint
icmpForwarder *tun.ICMPForwarder
udpForwarder *tun.UDPForwarder
writeBufs [][]byte
closeOnce sync.Once
}
func newSystemStackDevice(options DeviceOptions) (*systemStackDevice, error) {
@@ -51,10 +45,6 @@ func newSystemStackDevice(options DeviceOptions) (*systemStackDevice, error) {
if err != nil {
return nil, err
}
var (
inet4Address netip.Addr
inet6Address netip.Addr
)
for _, prefix := range options.Address {
addr := tun.AddressFromAddr(prefix.Addr())
protoAddr := tcpip.ProtocolAddress{
@@ -64,10 +54,8 @@ func newSystemStackDevice(options DeviceOptions) (*systemStackDevice, error) {
},
}
if prefix.Addr().Is4() {
inet4Address = prefix.Addr()
protoAddr.Protocol = ipv4.ProtocolNumber
} else {
inet6Address = prefix.Addr()
protoAddr.Protocol = ipv6.ProtocolNumber
}
gErr := ipStack.AddProtocolAddress(tun.DefaultNIC, protoAddr, stack.AddressProperties{})
@@ -75,27 +63,50 @@ func newSystemStackDevice(options DeviceOptions) (*systemStackDevice, error) {
return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", gErr.String())
}
}
if options.Handler != nil {
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket)
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket)
icmpForwarder := tun.NewICMPForwarder(options.Context, ipStack, options.Handler, options.ICMPTimeout)
icmpForwarder.SetLocalAddresses(inet4Address, inet6Address)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
}
return &systemStackDevice{
ctx: options.Context,
logger: options.Logger,
stackDevice := &systemStackDevice{
systemDevice: system,
stack: ipStack,
endpoint: endpoint,
}, nil
}
if options.Handler != nil {
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket)
udpForwarder := tun.NewUDPForwarder(options.Context, ipStack, options.Handler, tun.UDPNatOptions{
Timeout: options.UDPTimeout,
Shared: true,
Mapping: options.UDPMapping,
Filtering: options.UDPFiltering,
MaxSize: options.UDPNATMax,
InterfaceFinder: options.InterfaceFinder,
ExcludeInterface: []string{options.Name},
})
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)
stackDevice.udpForwarder = udpForwarder
icmpForwarder := tun.NewICMPForwarder(ipStack, options.Handler, options.Logger)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
stackDevice.icmpForwarder = icmpForwarder
}
return stackDevice, nil
}
func (w *systemStackDevice) SetDevice(device *device.Device) {
w.endpoint.device = device
}
func (w *systemStackDevice) Start() error {
if w.udpForwarder != nil {
err := w.udpForwarder.Start()
if err != nil {
return err
}
}
err := w.systemDevice.Start()
if err != nil && w.udpForwarder != nil {
_ = w.udpForwarder.Close()
}
return err
}
func (w *systemStackDevice) Write(bufs [][]byte, offset int) (count int, err error) {
if w.batchDevice != nil {
w.writeBufs = w.writeBufs[:0]
@@ -129,6 +140,12 @@ func (w *systemStackDevice) Close() error {
var err error
w.closeOnce.Do(func() {
close(w.endpoint.done)
if w.icmpForwarder != nil {
w.icmpForwarder.Close()
}
if w.udpForwarder != nil {
_ = w.udpForwarder.Close()
}
w.stack.Close()
for _, endpoint := range w.stack.CleanupEndpoints() {
endpoint.Abort()
@@ -165,23 +182,6 @@ func (w *systemStackDevice) writeStack(packet []byte) bool {
return true
}
func (w *systemStackDevice) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
ctx := log.ContextWithNewID(w.ctx)
destination, err := ping.ConnectGVisor(
ctx, w.logger,
metadata.Source.Addr, metadata.Destination.Addr,
routeContext,
w.stack,
w.inet4Address, w.inet6Address,
timeout,
)
if err != nil {
return nil, err
}
w.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
return destination, nil
}
type deviceEndpoint struct {
mtu uint32
done chan struct{}
+68 -56
View File
@@ -8,13 +8,9 @@ import (
"net"
"net/netip"
"os"
"reflect"
"strconv"
"strings"
"time"
"unsafe"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
@@ -36,9 +32,10 @@ type Endpoint struct {
ipcConf string
allowedAddress []netip.Prefix
tunDevice Device
natDevice NatDevice
returnDevice *returnDeviceWrapper
device *device.Device
allowedIPs *device.AllowedIPs
egressPool *tun.UDPEgressPool
pause pause.Manager
pauseCallback *list.Element[pause.Callback]
}
@@ -99,60 +96,48 @@ func NewEndpoint(options EndpointOptions) (*Endpoint, error) {
options.MTU = 1408
}
deviceOptions := DeviceOptions{
Context: options.Context,
Logger: options.Logger,
System: options.System,
Handler: options.Handler,
UDPTimeout: options.UDPTimeout,
ICMPTimeout: options.ICMPTimeout,
CreateDialer: options.CreateDialer,
Name: options.Name,
MTU: options.MTU,
Address: options.Address,
AllowedAddress: allowedAddresses,
Context: options.Context,
Logger: options.Logger,
System: options.System,
Handler: options.Handler,
UDPTimeout: options.UDPTimeout,
ICMPTimeout: options.ICMPTimeout,
UDPMapping: options.UDPMapping,
UDPFiltering: options.UDPFiltering,
UDPNATMax: options.UDPNATMax,
InterfaceFinder: options.InterfaceFinder,
CreateDialer: options.CreateDialer,
Name: options.Name,
MTU: options.MTU,
Address: options.Address,
AllowedAddress: allowedAddresses,
}
tunDevice, err := NewDevice(deviceOptions)
if err != nil {
return nil, E.Cause(err, "create WireGuard device")
}
natDevice, isNatDevice := tunDevice.(NatDevice)
if !isNatDevice {
natDevice = NewNATDevice(options.Context, options.Logger, tunDevice)
}
return &Endpoint{
options: options,
peers: peers,
ipcConf: ipcConf,
allowedAddress: allowedAddresses,
tunDevice: tunDevice,
natDevice: natDevice,
returnDevice: &returnDeviceWrapper{Device: tunDevice},
}, nil
}
func (e *Endpoint) Start(resolve bool) error {
if common.Any(e.peers, func(peer peerConfig) bool {
return !peer.endpoint.IsValid() && peer.destination.IsDomain()
}) {
if !resolve {
return nil
}
for peerIndex, peer := range e.peers {
if peer.endpoint.IsValid() || !peer.destination.IsDomain() {
continue
}
destinationAddress, err := e.options.ResolvePeer(peer.destination.Fqdn)
if err != nil {
return E.Cause(err, "resolve endpoint domain for peer[", peerIndex, "]: ", peer.destination)
}
e.peers[peerIndex].endpoint = netip.AddrPortFrom(destinationAddress, peer.destination.Port)
}
} else if resolve {
func (e *Endpoint) Start(postStart bool) error {
hasDomainPeer := common.Any(e.peers, func(peer peerConfig) bool {
return peer.destination.IsDomain()
})
if postStart != hasDomainPeer {
return nil
}
var bind conn.Bind
wgListener, isWgListener := common.Cast[dialer.WireGuardListener](e.options.Dialer)
if isWgListener {
bind = conn.NewDefaultBind(wgListener.WireGuardControl())
udpListener, isUDPListener := common.Cast[dialer.UDPListener](e.options.Dialer)
if isUDPListener {
listenerControl, _ := udpListener.UDPListenerControl()
bind = conn.NewDefaultBind(listenerControl)
} else {
var (
isConnect bool
@@ -176,13 +161,7 @@ func (e *Endpoint) Start(resolve bool) error {
e.options.Logger.Error(fmt.Sprintf(strings.ToLower(format), args...))
},
}
var deviceInput Device
if e.natDevice != nil {
deviceInput = e.natDevice
} else {
deviceInput = e.tunDevice
}
wgDevice := device.NewDevice(e.options.Context, deviceInput, bind, logger, e.options.Workers, e.options.PreallocatedBuffersPerPool, e.options.DisablePauses)
wgDevice := device.NewDevice(e.options.Context, e.returnDevice, bind, logger, e.options.Workers, e.options.PreallocatedBuffersPerPool, e.options.DisablePauses)
e.tunDevice.SetDevice(wgDevice)
var ipcConf strings.Builder
ipcConf.WriteString(e.ipcConf)
@@ -269,12 +248,40 @@ func (e *Endpoint) Start(resolve bool) error {
wgDevice.Close()
return E.Cause(err, "setup wireguard: \n", ipcConf.String())
}
for _, peer := range e.peers {
if !peer.destination.IsDomain() {
continue
}
var publicKey device.NoisePublicKey
common.Must(publicKey.FromHex(peer.publicKeyHex))
wgPeer, found := wgDevice.LookupActivePeer(publicKey)
if !found {
wgDevice.Close()
return E.New("missing configured peer: ", peer.destination)
}
wgPeer.SetEndpointResolver(func() ([]conn.Endpoint, error) {
addresses, lookupErr := e.options.ResolvePeer(peer.destination.Fqdn)
if lookupErr != nil {
return nil, lookupErr
}
endpoints := make([]conn.Endpoint, 0, len(addresses))
for _, address := range addresses {
destination := netip.AddrPortFrom(address, peer.destination.Port)
endpoint, parseErr := bind.ParseEndpoint(destination.String())
if parseErr != nil {
return nil, parseErr
}
endpoints = append(endpoints, endpoint)
}
return endpoints, nil
})
}
e.device = wgDevice
e.pause = service.FromContext[pause.Manager](e.options.Context)
if e.pause != nil {
e.pauseCallback = e.pause.RegisterCallback(e.onPauseUpdated)
}
e.allowedIPs = (*device.AllowedIPs)(unsafe.Pointer(reflect.Indirect(reflect.ValueOf(wgDevice)).FieldByName("allowedips").UnsafeAddr()))
e.allowedIPs = wgDevice.AllowedIPs()
return nil
}
@@ -297,26 +304,31 @@ func (e *Endpoint) Close() error {
e.pause.UnregisterCallback(e.pauseCallback)
e.pauseCallback = nil
}
if e.egressPool != nil {
e.egressPool.Close()
e.egressPool = nil
}
if e.device != nil {
e.device.Down()
e.device.Close()
e.device = nil
return nil
}
return nil
return e.tunDevice.Close()
}
func (e *Endpoint) Lookup(address netip.Addr) *device.Peer {
if e.allowedIPs == nil {
return nil
}
return e.allowedIPs.Lookup(address.AsSlice())
return e.allowedIPs.LookupFromPacket(netip.Addr{}, address, nil)
}
func (e *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
if e.natDevice == nil {
return nil, os.ErrInvalid
func (e *Endpoint) BindUpdate() error {
if e.device == nil {
return nil
}
return e.natDevice.CreateDestination(metadata, routeContext, timeout)
return e.device.BindUpdate()
}
func (e *Endpoint) onPauseUpdated(event int) {
+16 -8
View File
@@ -5,7 +5,8 @@ import (
"net/netip"
"time"
tun "github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
"github.com/sagernet/sing/common/json/badoption"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
@@ -13,20 +14,27 @@ import (
)
type EndpointOptions struct {
Context context.Context
Logger logger.ContextLogger
System bool
Handler tun.Handler
UDPTimeout time.Duration
ICMPTimeout time.Duration
Context context.Context
Logger logger.ContextLogger
System bool
Handler tun.Handler
UDPTimeout time.Duration
ICMPTimeout time.Duration
UDPMapping tun.NATMapping
UDPFiltering tun.NATFiltering
UDPNATMax uint32
InterfaceFinder control.InterfaceFinder
EgressPoolOptions tun.UDPEgressPoolOptions
Dialer N.Dialer
CreateDialer func(interfaceName string) N.Dialer
Tag string
Name string
MTU uint32
Address []netip.Prefix
PrivateKey string
ListenPort uint16
ResolvePeer func(domain string) (netip.Addr, error)
ResolvePeer func(domain string) ([]netip.Addr, error)
Peers []PeerOptions
Workers int
PreallocatedBuffersPerPool uint32
+157
View File
@@ -0,0 +1,157 @@
package wireguard
import (
"net/netip"
"sync/atomic"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/gtcpip/header"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/wireguard-go/device"
)
func (e *Endpoint) PortAddresses() (netip.Addr, netip.Addr) {
return e.tunDevice.Inet4Address(), e.tunDevice.Inet6Address()
}
func (e *Endpoint) PortMTU() uint32 {
return e.options.MTU
}
func (e *Endpoint) WritePackets(packets [][]byte) error {
wgDevice := e.device
if wgDevice == nil {
return E.New("WireGuard device is not ready")
}
packetRefs := make([]*device.InputPacketRef, 0, len(packets))
refs := make([]device.InputPacketRef, len(packets))
packetSlices := make([][]byte, len(packets))
for i, packet := range packets {
if len(packet) == 0 {
continue
}
var destination []byte
switch header.IPVersion(packet) {
case header.IPv4Version:
if len(packet) < header.IPv4MinimumSize {
continue
}
destination = header.IPv4(packet).DestinationAddressSlice()
case header.IPv6Version:
if len(packet) < header.IPv6MinimumSize {
continue
}
destination = header.IPv6(packet).DestinationAddressSlice()
default:
continue
}
packetSlices[i] = packet
refs[i] = device.InputPacketRef{
Destination: destination,
PacketSlices: packetSlices[i : i+1],
}
packetRefs = append(packetRefs, &refs[i])
}
if len(packetRefs) == 0 {
return nil
}
unmatchedRefs := wgDevice.InputPackets(packetRefs)
if len(unmatchedRefs) == 0 {
return nil
}
state := e.returnDevice.state.Load()
if state == nil {
return nil
}
var replies [][]byte
for _, packetRef := range unmatchedRefs {
packet := packetRef.PacketSlices[0]
var source netip.Addr
if header.IPVersion(packet) == header.IPv4Version {
source = e.tunDevice.Inet4Address()
} else {
source = e.tunDevice.Inet6Address()
}
reply, replyOk := tun.BuildUnreachable(packet, source, state.headroom)
if replyOk {
replies = append(replies, reply)
}
}
if len(replies) > 0 {
state.returnPath.ReturnPackets(replies)
}
return nil
}
func (e *Endpoint) AttachReturn(returnPath tun.Return) error {
headroom := returnPath.ReturnHeadroom()
if headroom > device.MessageTransportOffsetContent {
return E.New("return path headroom ", headroom, " exceeds available ", device.MessageTransportOffsetContent)
}
newState := &returnPathState{
returnPath: returnPath,
headroom: headroom,
}
for {
currentState := e.returnDevice.state.Load()
if currentState != nil {
if currentState.returnPath == returnPath {
return nil
}
return E.New("return path already attached")
}
if e.returnDevice.state.CompareAndSwap(nil, newState) {
return nil
}
}
}
func (e *Endpoint) DetachReturn(returnPath tun.Return) error {
currentState := e.returnDevice.state.Load()
if currentState != nil && currentState.returnPath == returnPath {
e.returnDevice.state.CompareAndSwap(currentState, nil)
}
return nil
}
type returnPathState struct {
returnPath tun.Return
headroom int
}
type returnDeviceWrapper struct {
Device
state atomic.Pointer[returnPathState]
}
func (d *returnDeviceWrapper) Write(bufs [][]byte, offset int) (int, error) {
state := d.state.Load()
if state == nil || len(bufs) == 0 {
return d.Device.Write(bufs, offset)
}
packets := make([][]byte, len(bufs))
for i, packet := range bufs {
// wireguard-go leaves device.MessageTransportOffsetContent writable bytes in front of the decrypted packet.
packets[i] = packet[offset-state.headroom:]
}
unconsumed := state.returnPath.ReturnPackets(packets)
if len(unconsumed) == 0 {
return 0, nil
}
if len(unconsumed) == len(bufs) {
return d.Device.Write(bufs, offset)
}
remaining := make([][]byte, 0, len(unconsumed))
searchIndex := 0
for _, packet := range unconsumed {
for searchIndex < len(bufs) && &packet[0] != &bufs[searchIndex][offset-state.headroom] {
searchIndex++
}
if searchIndex == len(bufs) {
break
}
remaining = append(remaining, bufs[searchIndex])
searchIndex++
}
return d.Device.Write(remaining, offset)
}