Add tun inbound for windows

This commit is contained in:
世界 2022-07-13 19:01:20 +08:00
parent 4fc763cfa2
commit e7d557fd9e
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
17 changed files with 116 additions and 201 deletions

View file

@ -1,4 +1,4 @@
//go:build !linux
//go:build !linux && !windows
package dialer

View file

@ -0,0 +1,68 @@
package dialer
import (
"encoding/binary"
"net"
"net/netip"
"syscall"
"unsafe"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
"golang.org/x/sys/windows"
)
const (
IP_UNICAST_IF = 31
IPV6_UNICAST_IF = 31
)
func bind4(handle windows.Handle, ifaceIdx int) error {
var bytes [4]byte
binary.BigEndian.PutUint32(bytes[:], uint32(ifaceIdx))
idx := *(*uint32)(unsafe.Pointer(&bytes[0]))
return windows.SetsockoptInt(handle, windows.IPPROTO_IP, IP_UNICAST_IF, int(idx))
}
func bind6(handle windows.Handle, ifaceIdx int) error {
return windows.SetsockoptInt(handle, windows.IPPROTO_IPV6, IPV6_UNICAST_IF, int(ifaceIdx))
}
func BindToInterface(router adapter.Router) control.Func {
return func(network, address string, conn syscall.RawConn) error {
interfaceName := router.DefaultInterfaceName()
if interfaceName == "" {
return nil
}
ipStr, _, err := net.SplitHostPort(address)
if err == nil {
if ip, err := netip.ParseAddr(ipStr); err == nil && !ip.IsGlobalUnicast() {
return err
}
}
var innerErr error
err = conn.Control(func(fd uintptr) {
handle := windows.Handle(fd)
// handle ip empty, e.g. net.Listen("udp", ":0")
if ipStr == "" {
innerErr = bind4(handle, router.DefaultInterfaceIndex())
if innerErr != nil {
return
}
// try bind ipv6, if failed, ignore. it's a workaround for windows disable interface ipv6
bind6(handle, router.DefaultInterfaceIndex())
return
}
switch network {
case "tcp4", "udp4", "ip4":
innerErr = bind4(handle, router.DefaultInterfaceIndex())
case "tcp6", "udp6":
innerErr = bind6(handle, router.DefaultInterfaceIndex())
}
})
return E.Errors(innerErr, err)
}
}

View file

@ -1,9 +0,0 @@
package iffmonitor
import "github.com/sagernet/sing-box/adapter"
type InterfaceMonitor interface {
adapter.Service
DefaultInterfaceName() string
DefaultInterfaceIndex() int
}

View file

@ -1,108 +0,0 @@
package iffmonitor
import (
"os"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
"github.com/vishvananda/netlink"
)
var _ InterfaceMonitor = (*monitor)(nil)
type monitor struct {
logger log.Logger
defaultInterfaceName string
defaultInterfaceIndex int
update chan netlink.RouteUpdate
close chan struct{}
}
func New(logger log.Logger) (InterfaceMonitor, error) {
return &monitor{
logger: logger,
update: make(chan netlink.RouteUpdate, 2),
close: make(chan struct{}),
}, nil
}
func (m *monitor) Start() error {
err := netlink.RouteSubscribe(m.update, m.close)
if err != nil {
return err
}
err = m.checkUpdate()
if err != nil {
return err
}
go m.loopUpdate()
return nil
}
func (m *monitor) loopUpdate() {
for {
select {
case <-m.close:
return
case <-m.update:
err := m.checkUpdate()
if err != nil {
m.logger.Error(E.Cause(err, "check default interface"))
}
}
}
}
func (m *monitor) checkUpdate() error {
routes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
if err != nil {
return err
}
for _, route := range routes {
if route.Dst != nil {
continue
}
var link netlink.Link
link, err = netlink.LinkByIndex(route.LinkIndex)
if err != nil {
return err
}
if link.Type() == "tuntap" {
continue
}
oldInterface := m.defaultInterfaceName
oldIndex := m.defaultInterfaceIndex
m.defaultInterfaceName = link.Attrs().Name
m.defaultInterfaceIndex = link.Attrs().Index
if oldInterface == m.defaultInterfaceName && oldIndex == m.defaultInterfaceIndex {
return nil
}
m.logger.Info("updated default interface ", m.defaultInterfaceName, ", index ", m.defaultInterfaceIndex)
return nil
}
return E.New("no route to internet")
}
func (m *monitor) Close() error {
select {
case <-m.close:
return os.ErrClosed
default:
}
close(m.close)
return nil
}
func (m *monitor) DefaultInterfaceName() string {
return m.defaultInterfaceName
}
func (m *monitor) DefaultInterfaceIndex() int {
return m.defaultInterfaceIndex
}

View file

@ -1,13 +0,0 @@
//go:build !linux
package iffmonitor
import (
"os"
"github.com/sagernet/sing-box/log"
)
func New(logger log.Logger) (InterfaceMonitor, error) {
return nil, os.ErrInvalid
}

View file

@ -15,6 +15,7 @@
| Type | Format |
|---------------|------------------------------|
| `tun` | [Tun](./tun) |
| `direct` | [Direct](./direct) |
| `mixed` | [Mixed](./mixed) |
| `socks` | [Socks](./socks) |

View file

@ -1,6 +1,6 @@
!!! error ""
Linux only
Linux and Windows only
### Structure

View file

@ -28,7 +28,7 @@ Default outbound tag. the first outbound will be used if empty.
!!! error ""
Linux only
Linux and Windows only
Bind outbound connections to the default NIC by default to prevent routing loops under Tun.

6
go.mod
View file

@ -10,12 +10,12 @@ require (
github.com/sagernet/sing v0.0.0-20220712060558-029ab1ce4f91
github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649
github.com/sagernet/sing-tun v0.0.0-20220711091522-4f7247190c96
github.com/sagernet/sing-tun v0.0.0-20220713125153-6c2c28da9d76
github.com/spf13/cobra v1.5.0
github.com/stretchr/testify v1.8.0
github.com/vishvananda/netlink v1.1.0
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
golang.org/x/net v0.0.0-20220708220712-1185a9018129
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e
)
require (
@ -26,8 +26,8 @@ require (
github.com/kr/pretty v0.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/vishvananda/netlink v1.1.0 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

8
go.sum
View file

@ -31,8 +31,8 @@ github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619 h1:oHbOmq1WS0XaZ
github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619/go.mod h1:y2fpvoxukw3G7eApIZwkcpcG/NE4AB8pCQI0Qd8rMqk=
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 h1:whNDUGOAX5GPZkSy4G3Gv9QyIgk5SXRyjkRuP7ohF8k=
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649/go.mod h1:MuyT+9fEPjvauAv0fSE0a6Q+l0Tv2ZrAafTkYfnxBFw=
github.com/sagernet/sing-tun v0.0.0-20220711091522-4f7247190c96 h1:BPsCEEKmww4PCuL2qCKGpwuS/HllNz4/G7EjvSHlXXg=
github.com/sagernet/sing-tun v0.0.0-20220711091522-4f7247190c96/go.mod h1:OLQnVTGk8NMVdoegQvenGHsGEv3diSMWe9Uh02cel0E=
github.com/sagernet/sing-tun v0.0.0-20220713125153-6c2c28da9d76 h1:/nvko0np1sAZrY5s+0HIn99SXbAkN7lPWiBZ22nDEL0=
github.com/sagernet/sing-tun v0.0.0-20220713125153-6c2c28da9d76/go.mod h1:oIK1kg8hkeA5zNSv9BcbTPzdR00bbVBt6eYvJp+rsck=
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@ -53,8 +53,8 @@ golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6fl
golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d h1:/m5NbqQelATgoSPVC2Z23sR4kVNokFwDDyWh/3rGY+I=
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e h1:NHvCuwuS43lGnYhten69ZWqi2QOj/CiDNcKbVqwVoew=
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View file

@ -1,12 +1,9 @@
//go:build !no_tun
package inbound
import (
"context"
"net"
"net/netip"
"os"
"runtime"
"strconv"
"strings"
@ -17,6 +14,7 @@ import (
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
M "github.com/sagernet/sing/common/metadata"
@ -40,8 +38,8 @@ type Tun struct {
autoRoute bool
hijackDNS bool
tunFd uintptr
tun *tun.GVisorTun
tunIf tun.Tun
tunStack *tun.GVisorTun
}
func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions) (*Tun, error) {
@ -77,17 +75,13 @@ func (t *Tun) Tag() string {
}
func (t *Tun) Start() error {
tunFd, err := tun.Open(t.tunName)
if err != nil {
return E.Cause(err, "create tun interface")
}
err = tun.Configure(t.tunName, t.inet4Address, t.inet6Address, t.tunMTU, t.autoRoute)
tunIf, err := tun.Open(t.tunName, t.inet4Address, t.inet6Address, t.tunMTU, t.autoRoute)
if err != nil {
return E.Cause(err, "configure tun interface")
}
t.tunFd = tunFd
t.tun = tun.NewGVisor(t.ctx, tunFd, t.tunMTU, t)
err = t.tun.Start()
t.tunIf = tunIf
t.tunStack = tun.NewGVisor(t.ctx, tunIf, t.tunMTU, t)
err = t.tunStack.Start()
if err != nil {
return err
}
@ -96,13 +90,9 @@ func (t *Tun) Start() error {
}
func (t *Tun) Close() error {
err := tun.UnConfigure(t.tunName, t.inet4Address, t.inet6Address, t.autoRoute)
if err != nil {
return err
}
return E.Errors(
t.tun.Close(),
os.NewFile(t.tunFd, "tun").Close(),
return common.Close(
t.tunStack,
t.tunIf,
)
}

View file

@ -1,16 +0,0 @@
//go:build no_tun
package inbound
import (
"context"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions) (adapter.Inbound, error) {
return nil, E.New("tun disabled in this build")
}

View file

@ -63,31 +63,31 @@ func (l *simpleLogger) Log(ctx context.Context, level Level, args []any) {
}
func (l *simpleLogger) Trace(args ...any) {
l.Log(nil, LevelTrace, args)
l.TraceContext(context.Background(), args...)
}
func (l *simpleLogger) Debug(args ...any) {
l.Log(nil, LevelDebug, args)
l.DebugContext(context.Background(), args...)
}
func (l *simpleLogger) Info(args ...any) {
l.Log(nil, LevelInfo, args)
l.InfoContext(context.Background(), args...)
}
func (l *simpleLogger) Warn(args ...any) {
l.Log(nil, LevelWarn, args)
l.WarnContext(context.Background(), args...)
}
func (l *simpleLogger) Error(args ...any) {
l.Log(nil, LevelError, args)
l.ErrorContext(context.Background(), args...)
}
func (l *simpleLogger) Fatal(args ...any) {
l.Log(nil, LevelFatal, args)
l.FatalContext(context.Background(), args...)
}
func (l *simpleLogger) Panic(args ...any) {
l.Log(nil, LevelPanic, args)
l.PanicContext(context.Background(), args...)
}
func (l *simpleLogger) TraceContext(ctx context.Context, args ...any) {

View file

@ -80,31 +80,31 @@ func (l *observableLogger) Log(ctx context.Context, level Level, args []any) {
}
func (l *observableLogger) Trace(args ...any) {
l.Log(nil, LevelTrace, args)
l.TraceContext(context.Background(), args...)
}
func (l *observableLogger) Debug(args ...any) {
l.Log(nil, LevelDebug, args)
l.DebugContext(context.Background(), args...)
}
func (l *observableLogger) Info(args ...any) {
l.Log(nil, LevelInfo, args)
l.InfoContext(context.Background(), args...)
}
func (l *observableLogger) Warn(args ...any) {
l.Log(nil, LevelWarn, args)
l.WarnContext(context.Background(), args...)
}
func (l *observableLogger) Error(args ...any) {
l.Log(nil, LevelError, args)
l.ErrorContext(context.Background(), args...)
}
func (l *observableLogger) Fatal(args ...any) {
l.Log(nil, LevelFatal, args)
l.FatalContext(context.Background(), args...)
}
func (l *observableLogger) Panic(args ...any) {
l.Log(nil, LevelPanic, args)
l.PanicContext(context.Background(), args...)
}
func (l *observableLogger) TraceContext(ctx context.Context, args ...any) {

View file

@ -17,12 +17,12 @@ import (
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/geoip"
"github.com/sagernet/sing-box/common/geosite"
"github.com/sagernet/sing-box/common/iffmonitor"
"github.com/sagernet/sing-box/common/sniff"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-dns"
tun "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
@ -65,7 +65,7 @@ type Router struct {
transportMap map[string]dns.Transport
autoDetectInterface bool
interfaceMonitor iffmonitor.InterfaceMonitor
interfaceMonitor tun.InterfaceMonitor
}
func NewRouter(ctx context.Context, logger log.ContextLogger, dnsLogger log.ContextLogger, options option.RouteOptions, dnsOptions option.DNSOptions) (*Router, error) {
@ -176,7 +176,7 @@ func NewRouter(ctx context.Context, logger log.ContextLogger, dnsLogger log.Cont
return nil, E.New("found circular reference in dns servers: ", strings.Join(unresolvedTags, " "))
}
var defaultTransport dns.Transport
if options.Final != "" {
if dnsOptions.Final != "" {
defaultTransport = dummyTransportMap[options.Final]
if defaultTransport == nil {
return nil, E.New("default dns server not found: ", options.Final)
@ -193,9 +193,11 @@ func NewRouter(ctx context.Context, logger log.ContextLogger, dnsLogger log.Cont
router.transportMap = transportMap
if options.AutoDetectInterface {
monitor, err := iffmonitor.New(router.logger)
monitor, err := tun.NewMonitor(func() {
router.logger.Info("updated default interface ", router.interfaceMonitor.DefaultInterfaceName(), ", index ", router.interfaceMonitor.DefaultInterfaceIndex())
})
if err != nil {
return nil, E.Cause(err, "create default interface monitor")
return nil, E.New("auto_detect_interface unsupported on current platform")
}
router.interfaceMonitor = monitor
}

View file

@ -35,11 +35,11 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619 // indirect
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 // indirect
github.com/sagernet/sing-tun v0.0.0-20220711091522-4f7247190c96 // indirect
github.com/sagernet/sing-tun v0.0.0-20220713125153-6c2c28da9d76 // indirect
github.com/vishvananda/netlink v1.1.0 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d // indirect
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e // indirect
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/v3 v3.3.0 // indirect

View file

@ -58,8 +58,8 @@ github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619 h1:oHbOmq1WS0XaZ
github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619/go.mod h1:y2fpvoxukw3G7eApIZwkcpcG/NE4AB8pCQI0Qd8rMqk=
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 h1:whNDUGOAX5GPZkSy4G3Gv9QyIgk5SXRyjkRuP7ohF8k=
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649/go.mod h1:MuyT+9fEPjvauAv0fSE0a6Q+l0Tv2ZrAafTkYfnxBFw=
github.com/sagernet/sing-tun v0.0.0-20220711091522-4f7247190c96 h1:BPsCEEKmww4PCuL2qCKGpwuS/HllNz4/G7EjvSHlXXg=
github.com/sagernet/sing-tun v0.0.0-20220711091522-4f7247190c96/go.mod h1:OLQnVTGk8NMVdoegQvenGHsGEv3diSMWe9Uh02cel0E=
github.com/sagernet/sing-tun v0.0.0-20220713125153-6c2c28da9d76 h1:/nvko0np1sAZrY5s+0HIn99SXbAkN7lPWiBZ22nDEL0=
github.com/sagernet/sing-tun v0.0.0-20220713125153-6c2c28da9d76/go.mod h1:oIK1kg8hkeA5zNSv9BcbTPzdR00bbVBt6eYvJp+rsck=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
@ -103,8 +103,8 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d h1:/m5NbqQelATgoSPVC2Z23sR4kVNokFwDDyWh/3rGY+I=
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e h1:NHvCuwuS43lGnYhten69ZWqi2QOj/CiDNcKbVqwVoew=
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=