mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-09-15 21:00:27 +00:00
Improve bridge
This commit is contained in:
@@ -2,15 +2,17 @@ package bridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/gtcpip/header"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
"github.com/sagernet/sing/service"
|
||||
@@ -31,8 +33,10 @@ type backendDarwin struct {
|
||||
inet4Local netip.Addr
|
||||
inet6Local netip.Addr
|
||||
|
||||
batchTUN tun.DarwinTUN
|
||||
|
||||
writeAccess sync.Mutex
|
||||
writeBuffer []byte
|
||||
writeBatch []*buf.Buffer
|
||||
|
||||
pfDevice *pfDevice
|
||||
pfToken uint64
|
||||
@@ -44,7 +48,7 @@ type backendDarwin struct {
|
||||
|
||||
func newBackend(ctx context.Context, logger logger.ContextLogger, networkManager adapter.NetworkManager, tag string, options option.BridgeOutboundOptions) (Backend, error) {
|
||||
instance := &backendDarwin{
|
||||
writeBuffer: make([]byte, tun.PacketOffset+maxPacketLength),
|
||||
writeBatch: make([]*buf.Buffer, 0, bridgeWriteBatchSize),
|
||||
}
|
||||
err := instance.init(ctx, logger, networkManager, tag, options)
|
||||
if err != nil {
|
||||
@@ -78,11 +82,13 @@ func (b *backendDarwin) start() error {
|
||||
b.tunName = tun.CalculateInterfaceName(b.bridgeName)
|
||||
b.anchorName = "com.apple/sing-box-" + b.tunName
|
||||
tunInterface, err := tun.New(tun.Options{
|
||||
Name: b.tunName,
|
||||
MTU: bridgeTunMTU,
|
||||
AutoRoute: false,
|
||||
InterfaceMonitor: b.networkManager.InterfaceMonitor(),
|
||||
Logger: b.logger,
|
||||
Name: b.tunName,
|
||||
MTU: bridgeTunMTU,
|
||||
AutoRoute: false,
|
||||
InterfaceMonitor: b.networkManager.InterfaceMonitor(),
|
||||
Logger: b.logger,
|
||||
EXP_ExternalConfiguration: true,
|
||||
EXP_MultiPendingPackets: true,
|
||||
})
|
||||
if err != nil {
|
||||
return E.Cause(err, "create bridge tun")
|
||||
@@ -106,11 +112,12 @@ func (b *backendDarwin) start() error {
|
||||
if err != nil {
|
||||
return E.Cause(err, "enable pf")
|
||||
}
|
||||
b.batchTUN = tunInterface.(tun.DarwinTUN)
|
||||
b.closed = make(chan struct{})
|
||||
b.readDone = make(chan struct{})
|
||||
b.registerMonitors(b.syncEgress)
|
||||
b.syncEgress()
|
||||
go b.readLoop()
|
||||
go b.batchReadLoop()
|
||||
b.logger.Info("bridge started at ", b.tunName, " (masquerade, egress ", b.egressLabel(), ")")
|
||||
return nil
|
||||
}
|
||||
@@ -137,6 +144,7 @@ func (b *backendDarwin) startPlatform() error {
|
||||
FileDescriptor: session.FileDescriptor(),
|
||||
Logger: b.logger,
|
||||
EXP_ExternalConfiguration: true,
|
||||
EXP_MultiPendingPackets: true,
|
||||
})
|
||||
if err != nil {
|
||||
return E.Cause(err, "create bridge tun")
|
||||
@@ -146,40 +154,16 @@ func (b *backendDarwin) startPlatform() error {
|
||||
if err != nil {
|
||||
return E.Cause(err, "start bridge tun")
|
||||
}
|
||||
b.batchTUN = tunInterface.(tun.DarwinTUN)
|
||||
b.closed = make(chan struct{})
|
||||
b.readDone = make(chan struct{})
|
||||
b.registerMonitors(b.syncSessionEgress)
|
||||
b.syncSessionEgress()
|
||||
go b.readLoop()
|
||||
go b.batchReadLoop()
|
||||
b.logger.Info("bridge started at ", b.tunName, " (platform, egress ", b.egressLabel(), ")")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *backendDarwin) registerMonitors(syncFunc func()) {
|
||||
var unregisterFuncs []func()
|
||||
networkMonitor := b.networkManager.NetworkMonitor()
|
||||
if networkMonitor != nil {
|
||||
networkElement := networkMonitor.RegisterCallback(syncFunc)
|
||||
unregisterFuncs = append(unregisterFuncs, func() { networkMonitor.UnregisterCallback(networkElement) })
|
||||
} else if b.boundInterface != "" {
|
||||
b.logger.Debug("network monitor unavailable, pinned egress will not track interface changes")
|
||||
}
|
||||
if b.boundInterface == "" {
|
||||
interfaceMonitor := b.networkManager.InterfaceMonitor()
|
||||
if interfaceMonitor != nil {
|
||||
interfaceElement := interfaceMonitor.RegisterCallback(func(_ *control.Interface, _ int) { syncFunc() })
|
||||
unregisterFuncs = append(unregisterFuncs, func() { interfaceMonitor.UnregisterCallback(interfaceElement) })
|
||||
}
|
||||
}
|
||||
if len(unregisterFuncs) > 0 {
|
||||
b.unregister = func() {
|
||||
for _, unregisterFunc := range unregisterFuncs {
|
||||
unregisterFunc()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backendDarwin) egressLabel() string {
|
||||
if b.boundInterface != "" {
|
||||
return b.boundInterface
|
||||
@@ -231,18 +215,27 @@ func (b *backendDarwin) PortMTU() uint32 {
|
||||
func (b *backendDarwin) WritePackets(packets [][]byte) error {
|
||||
b.writeAccess.Lock()
|
||||
defer b.writeAccess.Unlock()
|
||||
for _, packet := range packets {
|
||||
if len(packet) == 0 || len(packet) > maxPacketLength {
|
||||
for len(packets) > 0 {
|
||||
chunk := packets
|
||||
if len(chunk) > bridgeWriteBatchSize {
|
||||
chunk = chunk[:bridgeWriteBatchSize]
|
||||
}
|
||||
packets = packets[len(chunk):]
|
||||
batch := b.writeBatch[:0]
|
||||
for _, packet := range chunk {
|
||||
if len(packet) == 0 || len(packet) > maxPacketLength {
|
||||
continue
|
||||
}
|
||||
ipVersion := header.IPVersion(packet)
|
||||
if ipVersion != header.IPv4Version && ipVersion != header.IPv6Version {
|
||||
continue
|
||||
}
|
||||
batch = append(batch, buf.As(packet))
|
||||
}
|
||||
if len(batch) == 0 {
|
||||
continue
|
||||
}
|
||||
ipVersion := header.IPVersion(packet)
|
||||
if ipVersion != header.IPv4Version && ipVersion != header.IPv6Version {
|
||||
continue
|
||||
}
|
||||
buffer := b.writeBuffer[:tun.PacketOffset+len(packet)]
|
||||
tun.PacketFillHeader(buffer, ipVersion)
|
||||
copy(buffer[tun.PacketOffset:], packet)
|
||||
_, err := b.tunInterface.Write(buffer)
|
||||
err := b.batchTUN.BatchWrite(batch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -250,6 +243,81 @@ func (b *backendDarwin) WritePackets(packets [][]byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *backendDarwin) batchReadLoop() {
|
||||
defer close(b.readDone)
|
||||
headroom := -1
|
||||
var buffers [][]byte
|
||||
var batch [][]byte
|
||||
for {
|
||||
packets, err := b.batchTUN.BatchRead()
|
||||
if err != nil {
|
||||
select {
|
||||
case <-b.closed:
|
||||
return
|
||||
default:
|
||||
}
|
||||
if E.IsClosed(err) || errors.Is(err, syscall.EBADF) {
|
||||
return
|
||||
}
|
||||
b.logger.Debug(E.Cause(err, "bridge tun read"))
|
||||
continue
|
||||
}
|
||||
if len(packets) == 0 {
|
||||
continue
|
||||
}
|
||||
b.returnAccess.Lock()
|
||||
returnPaths := b.returnPaths
|
||||
b.returnAccess.Unlock()
|
||||
if len(returnPaths) == 0 {
|
||||
buf.ReleaseMulti(packets)
|
||||
continue
|
||||
}
|
||||
pathHeadroom := returnPaths[0].ReturnHeadroom()
|
||||
if pathHeadroom != headroom {
|
||||
headroom = pathHeadroom
|
||||
buffers = buffers[:0]
|
||||
}
|
||||
for len(buffers) < len(packets) {
|
||||
buffers = append(buffers, make([]byte, headroom+bridgeTunMTU))
|
||||
}
|
||||
batch = batch[:0]
|
||||
for _, packet := range packets {
|
||||
payload := packet.Bytes()
|
||||
if len(payload) == 0 {
|
||||
continue
|
||||
}
|
||||
fixReturnChecksum(payload)
|
||||
buffer := buffers[len(batch)][:headroom+len(payload)]
|
||||
copy(buffer[headroom:], payload)
|
||||
batch = append(batch, buffer)
|
||||
}
|
||||
buf.ReleaseMulti(packets)
|
||||
if len(batch) == 0 {
|
||||
continue
|
||||
}
|
||||
unconsumed := batch
|
||||
currentHeadroom := headroom
|
||||
for _, returnPath := range returnPaths {
|
||||
if len(unconsumed) == 0 {
|
||||
break
|
||||
}
|
||||
nextHeadroom := returnPath.ReturnHeadroom()
|
||||
if nextHeadroom != currentHeadroom {
|
||||
rebuffered := make([][]byte, 0, len(unconsumed))
|
||||
for _, packet := range unconsumed {
|
||||
payload := packet[currentHeadroom:]
|
||||
buffer := make([]byte, nextHeadroom+len(payload))
|
||||
copy(buffer[nextHeadroom:], payload)
|
||||
rebuffered = append(rebuffered, buffer)
|
||||
}
|
||||
unconsumed = rebuffered
|
||||
currentHeadroom = nextHeadroom
|
||||
}
|
||||
unconsumed = returnPath.ReturnPackets(unconsumed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backendDarwin) syncEgress() {
|
||||
b.egressAccess.Lock()
|
||||
defer b.egressAccess.Unlock()
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
const (
|
||||
defaultBridgeRuleIndex = 100
|
||||
defaultBridgeTableIndexBase = 2200
|
||||
bridgeWriteBatchSize = 32
|
||||
)
|
||||
|
||||
type backendLinux struct {
|
||||
@@ -83,12 +82,12 @@ func (b *backendLinux) start() error {
|
||||
b.tunName = tun.CalculateInterfaceName(b.bridgeName)
|
||||
b.nftTableName = "sing-box-" + b.tunName
|
||||
tunInterface, err := tun.New(tun.Options{
|
||||
Name: b.tunName,
|
||||
MTU: bridgeTunMTU,
|
||||
GSO: true,
|
||||
AutoRoute: false,
|
||||
InterfaceMonitor: b.networkManager.InterfaceMonitor(),
|
||||
Logger: b.logger,
|
||||
Name: b.tunName,
|
||||
MTU: bridgeTunMTU,
|
||||
GSO: true,
|
||||
InterfaceMonitor: b.networkManager.InterfaceMonitor(),
|
||||
Logger: b.logger,
|
||||
EXP_ExternalConfiguration: true,
|
||||
})
|
||||
if err != nil {
|
||||
return E.Cause(err, "create bridge tun")
|
||||
@@ -184,6 +183,7 @@ func (b *backendLinux) startPlatform() error {
|
||||
tunInterface, err := tun.New(tun.Options{
|
||||
Name: b.tunName,
|
||||
MTU: bridgeTunMTU,
|
||||
GSO: true,
|
||||
FileDescriptor: session.FileDescriptor(),
|
||||
Logger: b.logger,
|
||||
})
|
||||
@@ -195,9 +195,22 @@ func (b *backendLinux) startPlatform() error {
|
||||
if err != nil {
|
||||
return E.Cause(err, "start bridge tun")
|
||||
}
|
||||
linuxTUN := tunInterface.(tun.LinuxTUN)
|
||||
if linuxTUN.BatchSize() > 1 {
|
||||
b.batchTUN = linuxTUN
|
||||
b.writeHeadroom = linuxTUN.FrontHeadroom()
|
||||
b.writeBuffers = make([][]byte, bridgeWriteBatchSize)
|
||||
for i := range b.writeBuffers {
|
||||
b.writeBuffers[i] = make([]byte, b.writeHeadroom+maxPacketLength)
|
||||
}
|
||||
}
|
||||
b.closed = make(chan struct{})
|
||||
b.readDone = make(chan struct{})
|
||||
go b.readLoop()
|
||||
if b.batchTUN != nil {
|
||||
go b.batchReadLoop()
|
||||
} else {
|
||||
go b.readLoop()
|
||||
}
|
||||
monitor := b.networkManager.InterfaceMonitor()
|
||||
if monitor != nil {
|
||||
element := monitor.RegisterCallback(func(_ *control.Interface, _ int) { b.syncSessionEgress() })
|
||||
|
||||
+51
-10
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/adapter/outbound"
|
||||
@@ -22,9 +23,10 @@ func RegisterOutbound(registry *outbound.Registry) {
|
||||
}
|
||||
|
||||
var (
|
||||
_ adapter.Outbound = (*Outbound)(nil)
|
||||
_ adapter.FlowOutbound = (*Outbound)(nil)
|
||||
_ adapter.Lifecycle = (*Outbound)(nil)
|
||||
_ adapter.Outbound = (*Outbound)(nil)
|
||||
_ adapter.FlowOutbound = (*Outbound)(nil)
|
||||
_ adapter.OutboundWithPreferredRoutes = (*Outbound)(nil)
|
||||
_ adapter.Lifecycle = (*Outbound)(nil)
|
||||
)
|
||||
|
||||
type Backend interface {
|
||||
@@ -34,7 +36,10 @@ type Backend interface {
|
||||
|
||||
type Outbound struct {
|
||||
outbound.Adapter
|
||||
backend Backend
|
||||
logger log.ContextLogger
|
||||
networkManager adapter.NetworkManager
|
||||
platformInterface adapter.PlatformInterface
|
||||
backend Backend
|
||||
}
|
||||
|
||||
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.BridgeOutboundOptions) (adapter.Outbound, error) {
|
||||
@@ -44,8 +49,11 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
return nil, err
|
||||
}
|
||||
return &Outbound{
|
||||
Adapter: outbound.NewAdapter(C.TypeBridge, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, nil),
|
||||
backend: outboundBackend,
|
||||
Adapter: outbound.NewAdapter(C.TypeBridge, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, nil),
|
||||
logger: logger,
|
||||
networkManager: networkManager,
|
||||
platformInterface: service.FromContext[adapter.PlatformInterface](ctx),
|
||||
backend: outboundBackend,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -57,8 +65,41 @@ func (o *Outbound) Close() error {
|
||||
return o.backend.Close()
|
||||
}
|
||||
|
||||
func (o *Outbound) SupportsFlow(network string) bool {
|
||||
return true
|
||||
func (o *Outbound) PreferredDomain(metadata *adapter.InboundContext, domain string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *Outbound) PreferredAddress(metadata *adapter.InboundContext, address netip.Addr) bool {
|
||||
return metadata.PreMatch && !o.isLocalDestination(address)
|
||||
}
|
||||
|
||||
func (o *Outbound) PreMatchFlow(network string, destination netip.Addr) adapter.PreMatchAction {
|
||||
if o.isLocalDestination(destination) {
|
||||
o.logger.Warn("rejected connection to local destination ", destination, ": traffic to local addresses is not supported by bridge, exclude them in route rules")
|
||||
return adapter.PreMatchReject
|
||||
}
|
||||
return adapter.PreMatchFlow
|
||||
}
|
||||
|
||||
func (o *Outbound) isLocalDestination(destination netip.Addr) bool {
|
||||
if !destination.IsValid() {
|
||||
return false
|
||||
}
|
||||
destination = destination.Unmap()
|
||||
if destination.IsLoopback() || destination.IsUnspecified() {
|
||||
return true
|
||||
}
|
||||
if o.platformInterface != nil && slices.Contains(o.platformInterface.MyInterfaceAddress(), destination) {
|
||||
return true
|
||||
}
|
||||
for _, netInterface := range o.networkManager.InterfaceFinder().Interfaces() {
|
||||
for _, prefix := range netInterface.Addresses {
|
||||
if prefix.Addr() == destination {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *Outbound) PortAddresses() (netip.Addr, netip.Addr) {
|
||||
@@ -82,9 +123,9 @@ func (o *Outbound) WritePackets(packets [][]byte) error {
|
||||
}
|
||||
|
||||
func (o *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
return nil, E.New("Only L3 traffic is supported by bridge")
|
||||
return nil, E.New("only L3 traffic is supported by bridge")
|
||||
}
|
||||
|
||||
func (o *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
return nil, E.New("Only L3 traffic is supported by bridge")
|
||||
return nil, E.New("only L3 traffic is supported by bridge")
|
||||
}
|
||||
|
||||
@@ -13,9 +13,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
bridgeTunMTU = 1500
|
||||
maxPacketLength = 0xffff
|
||||
bridgeMaxInstances = 254
|
||||
bridgeTunMTU = 1500
|
||||
maxPacketLength = 0xffff
|
||||
bridgeMaxInstances = 254
|
||||
bridgeWriteBatchSize = 32
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -63,8 +63,8 @@ func buildBridgeAnchorRules(ruleLogger logger.ContextLogger, tunName string, egr
|
||||
}
|
||||
}
|
||||
// pf rules are last-match: the pass rules below override the route-to pin
|
||||
// for destinations in connected subnets and for addresses owned by the host
|
||||
// itself, so they reach local delivery on their own interface.
|
||||
// for destinations in connected subnets, so the routing table delivers them
|
||||
// on their own interface.
|
||||
for _, prefix := range localPrefixes {
|
||||
port := inet4Port
|
||||
if !prefix.Addr().Is4() {
|
||||
@@ -72,16 +72,6 @@ func buildBridgeAnchorRules(ruleLogger logger.ContextLogger, tunName string, egr
|
||||
}
|
||||
rules = append(rules, pfPassInRule(tunName, port, prefix))
|
||||
}
|
||||
for _, address := range hostAddresses() {
|
||||
port := inet4Port
|
||||
if !address.Is4() {
|
||||
port = inet6Port
|
||||
}
|
||||
if !port.IsValid() {
|
||||
continue
|
||||
}
|
||||
rules = append(rules, pfPassInRule(tunName, port, netip.PrefixFrom(address, address.BitLen())))
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
@@ -153,31 +143,6 @@ func collectLocalSegments(egress string, boundInterface string, inet4Active bool
|
||||
return
|
||||
}
|
||||
|
||||
// hostAddresses stands in for pfctl's `self`, which expands to every address
|
||||
// assigned to any interface at ruleset load time.
|
||||
func hostAddresses() []netip.Addr {
|
||||
interfaceAddrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var addresses []netip.Addr
|
||||
for _, interfaceAddr := range interfaceAddrs {
|
||||
ipNet, isIPNet := interfaceAddr.(*net.IPNet)
|
||||
if !isIPNet {
|
||||
continue
|
||||
}
|
||||
address, valid := netip.AddrFromSlice(ipNet.IP)
|
||||
if !valid {
|
||||
continue
|
||||
}
|
||||
address = address.Unmap()
|
||||
if !slices.Contains(addresses, address) {
|
||||
addresses = append(addresses, address)
|
||||
}
|
||||
}
|
||||
return addresses
|
||||
}
|
||||
|
||||
func pfScrubRule(egress string, port netip.Addr, maxMSS uint16) pfAnchorRule {
|
||||
rule := pfRule{
|
||||
Action: pfActionScrub,
|
||||
|
||||
@@ -3,6 +3,7 @@ package bridge
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/sagernet/netlink"
|
||||
"github.com/sagernet/sing-tun"
|
||||
@@ -65,10 +66,18 @@ func NewService(options ServiceOptions) (*Service, error) {
|
||||
func (s *Service) start(bridgeName string) error {
|
||||
s.tunName = tun.CalculateInterfaceName(bridgeName)
|
||||
s.nftTableName = "sing-box-" + s.tunName
|
||||
tunFileDescriptor, err := openBridgeTun(s.tunName)
|
||||
tunFileDescriptor, err := openTUN(s.tunName, true)
|
||||
if err != nil {
|
||||
return E.Cause(err, "create bridge tun")
|
||||
}
|
||||
err = setTCPOffload(tunFileDescriptor)
|
||||
if err != nil {
|
||||
s.logger.Warn(E.Cause(err, "set TCP offload"))
|
||||
}
|
||||
err = setUDPOffload(tunFileDescriptor)
|
||||
if err != nil {
|
||||
s.logger.Warn(E.Cause(err, "set UDP offload"))
|
||||
}
|
||||
s.tunFileDescriptor = tunFileDescriptor
|
||||
tunLink, err := netlink.LinkByName(s.tunName)
|
||||
if err != nil {
|
||||
@@ -223,29 +232,11 @@ func isDefaultDestination(destination *net.IPNet) bool {
|
||||
return ones == 0
|
||||
}
|
||||
|
||||
func openBridgeTun(name string) (int, error) {
|
||||
tunFileDescriptor, err := unix.Open("/dev/net/tun", unix.O_RDWR, 0)
|
||||
if err != nil {
|
||||
tunFileDescriptor, err = unix.Open("/dev/tun", unix.O_RDWR, 0)
|
||||
}
|
||||
if err != nil {
|
||||
return -1, E.Cause(err, "open tun control device")
|
||||
}
|
||||
ifreq, err := unix.NewIfreq(name)
|
||||
if err != nil {
|
||||
unix.Close(tunFileDescriptor)
|
||||
return -1, err
|
||||
}
|
||||
ifreq.SetUint16(unix.IFF_TUN | unix.IFF_NO_PI)
|
||||
err = unix.IoctlIfreq(tunFileDescriptor, unix.TUNSETIFF, ifreq)
|
||||
if err != nil {
|
||||
unix.Close(tunFileDescriptor)
|
||||
return -1, E.Cause(err, "TUNSETIFF")
|
||||
}
|
||||
err = unix.SetNonblock(tunFileDescriptor, true)
|
||||
if err != nil {
|
||||
unix.Close(tunFileDescriptor)
|
||||
return -1, E.Cause(err, "set nonblock")
|
||||
}
|
||||
return tunFileDescriptor, nil
|
||||
}
|
||||
//go:linkname openTUN github.com/sagernet/sing-tun.open
|
||||
func openTUN(name string, vnetHdr bool) (int, error)
|
||||
|
||||
//go:linkname setTCPOffload github.com/sagernet/sing-tun.setTCPOffload
|
||||
func setTCPOffload(fd int) error
|
||||
|
||||
//go:linkname setUDPOffload github.com/sagernet/sing-tun.setUDPOffload
|
||||
func setUDPOffload(fd int) error
|
||||
|
||||
Reference in New Issue
Block a user