mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-09-15 21:00:27 +00:00
Merge tag 'v1.14.0'
This commit is contained in:
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()) {
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}),
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user