Add auto_route and auto_detect_interface for linux

This commit is contained in:
世界 2022-07-10 08:18:52 +08:00
parent 4432cc2253
commit 638f8a52d1
No known key found for this signature in database
GPG Key ID: CD109927C34A63C4
16 changed files with 318 additions and 20 deletions

View File

@ -23,6 +23,9 @@ type Router interface {
Exchange(ctx context.Context, message *dnsmessage.Message) (*dnsmessage.Message, error)
Lookup(ctx context.Context, domain string, strategy C.DomainStrategy) ([]netip.Addr, error)
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
AutoDetectInterface() bool
DefaultInterfaceName() string
DefaultInterfaceIndex() int
}
type Rule interface {

View File

@ -0,0 +1,23 @@
package dialer
import (
"syscall"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
)
func BindToInterface(router adapter.Router) control.Func {
return func(network, address string, conn syscall.RawConn) error {
interfaceName := router.DefaultInterfaceName()
if interfaceName == "" {
return nil
}
var innerErr error
err := conn.Control(func(fd uintptr) {
innerErr = syscall.BindToDevice(int(fd), interfaceName)
})
return E.Errors(innerErr, err)
}
}

View File

@ -0,0 +1,12 @@
//go:build !linux
package dialer
import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common/control"
)
func BindToInterface(router adapter.Router) control.Func {
return nil
}

View File

@ -5,6 +5,7 @@ import (
"net"
"time"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/control"
@ -18,12 +19,15 @@ type DefaultDialer struct {
net.ListenConfig
}
func NewDefault(options option.DialerOptions) *DefaultDialer {
func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDialer {
var dialer net.Dialer
var listener net.ListenConfig
if options.BindInterface != "" {
dialer.Control = control.Append(dialer.Control, control.BindToInterface(options.BindInterface))
listener.Control = control.Append(listener.Control, control.BindToInterface(options.BindInterface))
} else if router.AutoDetectInterface() {
dialer.Control = BindToInterface(router)
listener.Control = BindToInterface(router)
}
if options.RoutingMark != 0 {
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))

View File

@ -12,7 +12,7 @@ import (
func New(router adapter.Router, options option.DialerOptions) N.Dialer {
if options.Detour == "" {
return NewDefault(options)
return NewDefault(router, options)
} else {
return NewDetour(router, options.Detour)
}

View File

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

View File

@ -0,0 +1,100 @@
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
}
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
}
m.defaultInterfaceName = link.Attrs().Name
m.defaultInterfaceIndex = link.Attrs().Index
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

@ -0,0 +1,13 @@
//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

@ -1,6 +1,7 @@
package tun
import (
"net"
"net/netip"
"github.com/vishvananda/netlink"
@ -15,7 +16,7 @@ func Open(name string) (uintptr, error) {
return uintptr(tunFd), nil
}
func Configure(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu uint32) error {
func Configure(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu uint32, autoRoute bool) error {
tunLink, err := netlink.LinkByName(name)
if err != nil {
return err
@ -47,5 +48,66 @@ func Configure(name string, inet4Address netip.Prefix, inet6Address netip.Prefix
return err
}
if autoRoute {
if inet4Address.IsValid() {
err = netlink.RouteAdd(&netlink.Route{
Dst: &net.IPNet{
IP: net.IPv4zero,
Mask: net.CIDRMask(0, 32),
},
LinkIndex: tunLink.Attrs().Index,
})
if err != nil {
return err
}
}
if inet6Address.IsValid() {
err = netlink.RouteAdd(&netlink.Route{
Dst: &net.IPNet{
IP: net.IPv6zero,
Mask: net.CIDRMask(0, 128),
},
LinkIndex: tunLink.Attrs().Index,
})
if err != nil {
return err
}
}
}
return nil
}
func UnConfigure(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, autoRoute bool) error {
if autoRoute {
tunLink, err := netlink.LinkByName(name)
if err != nil {
return err
}
if inet4Address.IsValid() {
err = netlink.RouteDel(&netlink.Route{
Dst: &net.IPNet{
IP: net.IPv4zero,
Mask: net.CIDRMask(0, 32),
},
LinkIndex: tunLink.Attrs().Index,
})
if err != nil {
return err
}
}
if inet6Address.IsValid() {
err = netlink.RouteDel(&netlink.Route{
Dst: &net.IPNet{
IP: net.IPv6zero,
Mask: net.CIDRMask(0, 128),
},
LinkIndex: tunLink.Attrs().Index,
})
if err != nil {
return err
}
}
}
return nil
}

View File

@ -3,9 +3,18 @@
package tun
import (
"net/netip"
"os"
)
func Open(name string) (uintptr, error) {
return 0, os.ErrInvalid
}
func Configure(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu uint32, autoRoute bool) error {
return os.ErrInvalid
}
func UnConfigure(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, autoRoute bool) error {
return os.ErrInvalid
}

View File

@ -236,7 +236,7 @@ func (a *myInboundAdapter) NewError(ctx context.Context, err error) {
func NewError(logger log.Logger, ctx context.Context, err error) {
common.Close(err)
if E.IsClosed(err) {
if E.IsClosed(err) || E.IsCanceled(err) {
logger.WithContext(ctx).Debug("connection closed")
return
}

View File

@ -32,8 +32,9 @@ type Tun struct {
logger log.Logger
options option.TunInboundOptions
tunFd uintptr
tun *tun.GVisorTun
tunName string
tunFd uintptr
tun *tun.GVisorTun
}
func NewTun(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options option.TunInboundOptions) (*Tun, error) {
@ -70,10 +71,11 @@ func (t *Tun) Start() error {
if err != nil {
return E.Cause(err, "create tun interface")
}
err = tun.Configure(tunName, netip.Prefix(t.options.Inet4Address), netip.Prefix(t.options.Inet6Address), mtu)
err = tun.Configure(tunName, netip.Prefix(t.options.Inet4Address), netip.Prefix(t.options.Inet6Address), mtu, t.options.AutoRoute)
if err != nil {
return E.Cause(err, "configure tun interface")
}
t.tunName = tunName
t.tunFd = tunFd
t.tun = tun.NewGVisor(t.ctx, tunFd, mtu, t)
err = t.tun.Start()
@ -85,6 +87,10 @@ func (t *Tun) Start() error {
}
func (t *Tun) Close() error {
err := tun.UnConfigure(t.tunName, netip.Prefix(t.options.Inet4Address), netip.Prefix(t.options.Inet6Address), t.options.AutoRoute)
if err != nil {
return err
}
return E.Errors(
t.tun.Close(),
os.NewFile(t.tunFd, "tun").Close(),
@ -99,6 +105,9 @@ func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata
metadata.Network = C.NetworkTCP
metadata.Source = upstreamMetadata.Source
metadata.Destination = upstreamMetadata.Destination
metadata.SniffEnabled = t.options.SniffEnabled
metadata.SniffOverrideDestination = t.options.SniffOverrideDestination
metadata.DomainStrategy = C.DomainStrategy(t.options.DomainStrategy)
return t.router.RouteConnection(ctx, conn, metadata)
}
@ -110,6 +119,9 @@ func (t *Tun) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstre
metadata.Network = C.NetworkUDP
metadata.Source = upstreamMetadata.Source
metadata.Destination = upstreamMetadata.Destination
metadata.SniffEnabled = t.options.SniffEnabled
metadata.SniffOverrideDestination = t.options.SniffOverrideDestination
metadata.DomainStrategy = C.DomainStrategy(t.options.DomainStrategy)
return t.router.RoutePacketConnection(ctx, conn, metadata)
}

View File

@ -83,16 +83,20 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error {
return nil
}
type ListenOptions struct {
Listen ListenAddress `json:"listen"`
ListenPort uint16 `json:"listen_port"`
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
UDPTimeout int64 `json:"udp_timeout,omitempty"`
type InboundOptions struct {
SniffEnabled bool `json:"sniff,omitempty"`
SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"`
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
}
type ListenOptions struct {
Listen ListenAddress `json:"listen"`
ListenPort uint16 `json:"listen_port"`
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
UDPTimeout int64 `json:"udp_timeout,omitempty"`
InboundOptions
}
type SimpleInboundOptions struct {
ListenOptions
Users []auth.User `json:"users,omitempty"`
@ -144,4 +148,6 @@ type TunInboundOptions struct {
MTU uint32 `json:"mtu,omitempty"`
Inet4Address ListenPrefix `json:"inet4_address"`
Inet6Address ListenPrefix `json:"inet6_address"`
AutoRoute bool `json:"auto_route"`
InboundOptions
}

View File

@ -9,10 +9,11 @@ import (
)
type RouteOptions struct {
GeoIP *GeoIPOptions `json:"geoip,omitempty"`
Geosite *GeositeOptions `json:"geosite,omitempty"`
Rules []Rule `json:"rules,omitempty"`
Final string `json:"final,omitempty"`
GeoIP *GeoIPOptions `json:"geoip,omitempty"`
Geosite *GeositeOptions `json:"geosite,omitempty"`
Rules []Rule `json:"rules,omitempty"`
Final string `json:"final,omitempty"`
AutoDetectInterface bool `json:"auto_detect_interface,omitempty"`
}
func (o RouteOptions) Equals(other RouteOptions) bool {

View File

@ -9,6 +9,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
@ -77,7 +78,12 @@ func (h *Direct) ListenPacket(ctx context.Context, destination M.Socksaddr) (net
}
func (h *Direct) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
return NewConnection(ctx, h, conn, metadata)
ctx = adapter.WithContext(ctx, &metadata)
outConn, err := h.DialContext(ctx, C.NetworkTCP, metadata.Destination)
if err != nil {
return err
}
return bufio.CopyConn(ctx, conn, outConn)
}
func (h *Direct) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {

View File

@ -16,6 +16,7 @@ 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/dns"
@ -62,6 +63,9 @@ type Router struct {
defaultTransport adapter.DNSTransport
transports []adapter.DNSTransport
transportMap map[string]adapter.DNSTransport
autoDetectInterface bool
interfaceMonitor iffmonitor.InterfaceMonitor
}
func NewRouter(ctx context.Context, logger log.Logger, options option.RouteOptions, dnsOptions option.DNSOptions) (*Router, error) {
@ -80,6 +84,7 @@ func NewRouter(ctx context.Context, logger log.Logger, options option.RouteOptio
defaultDetour: options.Final,
dnsClient: dns.NewClient(dnsOptions.DNSClientOptions),
defaultDomainStrategy: C.DomainStrategy(dnsOptions.Strategy),
autoDetectInterface: options.AutoDetectInterface,
}
for i, ruleOptions := range options.Rules {
routeRule, err := NewRule(router, logger, ruleOptions)
@ -181,6 +186,14 @@ func NewRouter(ctx context.Context, logger log.Logger, options option.RouteOptio
router.defaultTransport = defaultTransport
router.transports = transports
router.transportMap = transportMap
if options.AutoDetectInterface {
monitor, err := iffmonitor.New(router.logger)
if err != nil {
return nil, E.Cause(err, "create default interface monitor")
}
router.interfaceMonitor = monitor
}
return router, nil
}
@ -303,6 +316,12 @@ func (r *Router) Start() error {
r.geositeCache = nil
r.geositeReader = nil
}
if r.interfaceMonitor != nil {
err := r.interfaceMonitor.Start()
if err != nil {
return err
}
}
return nil
}
@ -321,6 +340,7 @@ func (r *Router) Close() error {
}
return common.Close(
common.PtrOrNil(r.geoIPReader),
r.interfaceMonitor,
)
}
@ -399,7 +419,7 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
}
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
if metadata.SniffEnabled {
if metadata.SniffEnabled && metadata.Destination.Port == 443 {
_buffer := buf.StackNewPacket()
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
@ -417,9 +437,9 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
metadata.Destination.Fqdn = metadata.Domain
}
if metadata.Domain != "" {
r.logger.WithContext(ctx).Info("sniffed protocol: ", metadata.Protocol, ", domain: ", metadata.Domain)
r.logger.WithContext(ctx).Info("sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain)
} else {
r.logger.WithContext(ctx).Info("sniffed protocol: ", metadata.Protocol)
r.logger.WithContext(ctx).Info("sniffed packet protocol: ", metadata.Protocol)
}
}
conn = bufio.NewCachedPacketConn(conn, buffer, originDestination)
@ -485,6 +505,24 @@ func (r *Router) matchDNS(ctx context.Context) adapter.DNSTransport {
return r.defaultTransport
}
func (r *Router) AutoDetectInterface() bool {
return r.autoDetectInterface
}
func (r *Router) DefaultInterfaceName() string {
if r.interfaceMonitor == nil {
return ""
}
return r.interfaceMonitor.DefaultInterfaceName()
}
func (r *Router) DefaultInterfaceIndex() int {
if r.interfaceMonitor == nil {
return 0
}
return r.interfaceMonitor.DefaultInterfaceIndex()
}
func hasGeoRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {
for _, rule := range rules {
switch rule.Type {