Feature: mark on socket (#1705)

This commit is contained in:
bobo liu 2021-11-08 16:59:48 +08:00 committed by GitHub
parent e622d8dd38
commit bd2ea2b917
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 119 additions and 22 deletions

View File

@ -16,6 +16,7 @@ type Base struct {
iface string iface string
tp C.AdapterType tp C.AdapterType
udp bool udp bool
rmark int
} }
// Name implements C.ProxyAdapter // Name implements C.ProxyAdapter
@ -66,19 +67,25 @@ func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
opts = append(opts, dialer.WithInterface(b.iface)) opts = append(opts, dialer.WithInterface(b.iface))
} }
if b.rmark != 0 {
opts = append(opts, dialer.WithRoutingMark(b.rmark))
}
return opts return opts
} }
type BasicOption struct { type BasicOption struct {
Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"` Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"`
RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"`
} }
type BaseOption struct { type BaseOption struct {
Name string Name string
Addr string Addr string
Type C.AdapterType Type C.AdapterType
UDP bool UDP bool
Interface string Interface string
RoutingMark int
} }
func NewBase(opt BaseOption) *Base { func NewBase(opt BaseOption) *Base {
@ -88,6 +95,7 @@ func NewBase(opt BaseOption) *Base {
tp: opt.Type, tp: opt.Type,
udp: opt.UDP, udp: opt.UDP,
iface: opt.Interface, iface: opt.Interface,
rmark: opt.RoutingMark,
} }
} }

View File

@ -24,7 +24,7 @@ type Socks5 struct {
} }
type Socks5Option struct { type Socks5Option struct {
*BaseOption BasicOption
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`

View File

@ -94,9 +94,10 @@ func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback { func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback {
return &Fallback{ return &Fallback{
Base: outbound.NewBase(outbound.BaseOption{ Base: outbound.NewBase(outbound.BaseOption{
Name: option.Name, Name: option.Name,
Type: C.Fallback, Type: C.Fallback,
Interface: option.Interface, Interface: option.Interface,
RoutingMark: option.RoutingMark,
}), }),
single: singledo.NewSingle(defaultGetProxiesDuration), single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers, providers: providers,

View File

@ -171,9 +171,10 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide
} }
return &LoadBalance{ return &LoadBalance{
Base: outbound.NewBase(outbound.BaseOption{ Base: outbound.NewBase(outbound.BaseOption{
Name: option.Name, Name: option.Name,
Type: C.LoadBalance, Type: C.LoadBalance,
Interface: option.Interface, Interface: option.Interface,
RoutingMark: option.RoutingMark,
}), }),
single: singledo.NewSingle(defaultGetProxiesDuration), single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers, providers: providers,

View File

@ -103,9 +103,10 @@ func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy {
func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay { func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
return &Relay{ return &Relay{
Base: outbound.NewBase(outbound.BaseOption{ Base: outbound.NewBase(outbound.BaseOption{
Name: option.Name, Name: option.Name,
Type: C.Relay, Type: C.Relay,
Interface: option.Interface, Interface: option.Interface,
RoutingMark: option.RoutingMark,
}), }),
single: singledo.NewSingle(defaultGetProxiesDuration), single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers, providers: providers,

View File

@ -101,9 +101,10 @@ func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider)
selected := providers[0].Proxies()[0].Name() selected := providers[0].Proxies()[0].Name()
return &Selector{ return &Selector{
Base: outbound.NewBase(outbound.BaseOption{ Base: outbound.NewBase(outbound.BaseOption{
Name: option.Name, Name: option.Name,
Type: C.Selector, Type: C.Selector,
Interface: option.Interface, Interface: option.Interface,
RoutingMark: option.RoutingMark,
}), }),
single: singledo.NewSingle(defaultGetProxiesDuration), single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers, providers: providers,

View File

@ -137,9 +137,10 @@ func parseURLTestOption(config map[string]interface{}) []urlTestOption {
func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest { func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest {
urlTest := &URLTest{ urlTest := &URLTest{
Base: outbound.NewBase(outbound.BaseOption{ Base: outbound.NewBase(outbound.BaseOption{
Name: option.Name, Name: option.Name,
Type: C.URLTest, Type: C.URLTest,
Interface: option.Interface, Interface: option.Interface,
RoutingMark: option.RoutingMark,
}), }),
single: singledo.NewSingle(defaultGetProxiesDuration), single: singledo.NewSingle(defaultGetProxiesDuration),
fastSingle: singledo.NewSingle(time.Second * 10), fastSingle: singledo.NewSingle(time.Second * 10),

View File

@ -59,6 +59,9 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio
if cfg.addrReuse { if cfg.addrReuse {
addrReuseToListenConfig(lc) addrReuseToListenConfig(lc)
} }
if cfg.routingMark != 0 {
bindMarkToListenConfig(cfg.routingMark, lc, network, address)
}
return lc.ListenPacket(ctx, network, address) return lc.ListenPacket(ctx, network, address)
} }
@ -82,6 +85,9 @@ func dialContext(ctx context.Context, network string, destination net.IP, port s
return nil, err return nil, err
} }
} }
if opt.routingMark != 0 {
bindMarkToDialer(opt.routingMark, dialer, network, destination)
}
return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port)) return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
} }

View File

@ -0,0 +1,44 @@
//go:build linux
// +build linux
package dialer
import (
"net"
"syscall"
)
func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ net.IP) {
dialer.Control = bindMarkToControl(mark, dialer.Control)
}
func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, address string) {
lc.Control = bindMarkToControl(mark, lc.Control)
}
func bindMarkToControl(mark int, chain controlFn) controlFn {
return func(network, address string, c syscall.RawConn) (err error) {
defer func() {
if err == nil && chain != nil {
err = chain(network, address, c)
}
}()
ipStr, _, err := net.SplitHostPort(address)
if err == nil {
ip := net.ParseIP(ipStr)
if ip != nil && !ip.IsGlobalUnicast() {
return
}
}
return c.Control(func(fd uintptr) {
switch network {
case "tcp4", "udp4":
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark)
case "tcp6", "udp6":
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark)
}
})
}
}

View File

@ -0,0 +1,27 @@
//go:build !linux
// +build !linux
package dialer
import (
"net"
"sync"
"github.com/Dreamacro/clash/log"
)
var printMarkWarnOnce sync.Once
func printMarkWarn() {
printMarkWarnOnce.Do(func() {
log.Warnln("Routing mark on socket is not supported on current platform")
})
}
func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ net.IP) {
printMarkWarn()
}
func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, address string) {
printMarkWarn()
}

View File

@ -10,6 +10,7 @@ var (
type option struct { type option struct {
interfaceName string interfaceName string
addrReuse bool addrReuse bool
routingMark int
} }
type Option func(opt *option) type Option func(opt *option)
@ -25,3 +26,9 @@ func WithAddrReuse(reuse bool) Option {
opt.addrReuse = reuse opt.addrReuse = reuse
} }
} }
func WithRoutingMark(mark int) Option {
return func(opt *option) {
opt.routingMark = mark
}
}