Merge branch 'Alpha' into Beta
This commit is contained in:
commit
c7aa16426f
@ -16,11 +16,11 @@ RUN go mod download &&\
|
||||
FROM alpine:latest
|
||||
LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/Clash.Meta"
|
||||
|
||||
RUN apk add --no-cache ca-certificates tzdata
|
||||
RUN apk add --no-cache ca-certificates tzdata iptables
|
||||
|
||||
VOLUME ["/root/.config/clash/"]
|
||||
|
||||
COPY --from=builder /clash-config/ /root/.config/clash/
|
||||
COPY --from=builder /clash /clash
|
||||
RUN chmod +x /clash
|
||||
ENTRYPOINT [ "/clash" ]
|
||||
ENTRYPOINT [ "/clash" ]
|
||||
|
2
Makefile
2
Makefile
@ -12,7 +12,7 @@ VERSION=$(shell git rev-parse --short HEAD)
|
||||
endif
|
||||
|
||||
BUILDTIME=$(shell date -u)
|
||||
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
||||
GOBUILD=CGO_ENABLED=0 go build -tags with_gvisor -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
||||
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
||||
-w -s -buildid='
|
||||
|
||||
|
@ -90,7 +90,7 @@ func (b *Base) Addr() string {
|
||||
}
|
||||
|
||||
// Unwrap implements C.ProxyAdapter
|
||||
func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
func (b *Base) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -4,27 +4,29 @@ import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
"github.com/Dreamacro/clash/transport/hysteria/core"
|
||||
"github.com/Dreamacro/clash/transport/hysteria/obfs"
|
||||
"github.com/Dreamacro/clash/transport/hysteria/pmtud_fix"
|
||||
"github.com/Dreamacro/clash/transport/hysteria/transport"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
hyCongestion "github.com/Dreamacro/clash/transport/hysteria/congestion"
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/Dreamacro/clash/transport/hysteria/core"
|
||||
"github.com/Dreamacro/clash/transport/hysteria/obfs"
|
||||
"github.com/Dreamacro/clash/transport/hysteria/pmtud_fix"
|
||||
"github.com/Dreamacro/clash/transport/hysteria/transport"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -85,23 +87,27 @@ func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata
|
||||
|
||||
type HysteriaOption struct {
|
||||
BasicOption
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Protocol string `proxy:"protocol,omitempty"`
|
||||
Up string `proxy:"up"`
|
||||
Down string `proxy:"down"`
|
||||
AuthString string `proxy:"auth_str,omitempty"`
|
||||
Obfs string `proxy:"obfs,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
ALPN string `proxy:"alpn,omitempty"`
|
||||
CustomCA string `proxy:"ca,omitempty"`
|
||||
CustomCAString string `proxy:"ca_str,omitempty"`
|
||||
ReceiveWindowConn int `proxy:"recv_window_conn,omitempty"`
|
||||
ReceiveWindow int `proxy:"recv_window,omitempty"`
|
||||
DisableMTUDiscovery bool `proxy:"disable_mtu_discovery,omitempty"`
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Protocol string `proxy:"protocol,omitempty"`
|
||||
ObfsProtocol string `proxy:"obfs-protocol,omitempty"` // compatible with Stash
|
||||
Up string `proxy:"up"`
|
||||
UpSpeed int `proxy:"up-speed,omitempty"` // compatible with Stash
|
||||
Down string `proxy:"down"`
|
||||
DownSpeed int `proxy:"down-speed,omitempty"` // compatible with Stash
|
||||
Auth string `proxy:"auth,omitempty"`
|
||||
AuthString string `proxy:"auth_str,omitempty"`
|
||||
Obfs string `proxy:"obfs,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
CustomCA string `proxy:"ca,omitempty"`
|
||||
CustomCAString string `proxy:"ca_str,omitempty"`
|
||||
ReceiveWindowConn int `proxy:"recv_window_conn,omitempty"`
|
||||
ReceiveWindow int `proxy:"recv_window,omitempty"`
|
||||
DisableMTUDiscovery bool `proxy:"disable_mtu_discovery,omitempty"`
|
||||
}
|
||||
|
||||
func (c *HysteriaOption) Speed() (uint64, uint64, error) {
|
||||
@ -172,7 +178,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
}
|
||||
|
||||
if len(option.ALPN) > 0 {
|
||||
tlsConfig.NextProtos = []string{option.ALPN}
|
||||
tlsConfig.NextProtos = option.ALPN
|
||||
} else {
|
||||
tlsConfig.NextProtos = []string{DefaultALPN}
|
||||
}
|
||||
@ -186,6 +192,9 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
DisablePathMTUDiscovery: option.DisableMTUDiscovery,
|
||||
EnableDatagrams: true,
|
||||
}
|
||||
if option.ObfsProtocol != "" {
|
||||
option.Protocol = option.ObfsProtocol
|
||||
}
|
||||
if option.Protocol == "" {
|
||||
option.Protocol = DefaultProtocol
|
||||
}
|
||||
@ -202,6 +211,12 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
}
|
||||
|
||||
var auth = []byte(option.AuthString)
|
||||
if option.Auth != "" {
|
||||
auth, err = base64.StdEncoding.DecodeString(option.Auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var obfuscator obfs.Obfuscator
|
||||
if len(option.Obfs) > 0 {
|
||||
obfuscator = obfs.NewXPlusObfuscator([]byte(option.Obfs))
|
||||
@ -211,7 +226,12 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if option.UpSpeed != 0 {
|
||||
up = uint64(option.UpSpeed * mbpsToBps)
|
||||
}
|
||||
if option.DownSpeed != 0 {
|
||||
down = uint64(option.DownSpeed * mbpsToBps)
|
||||
}
|
||||
client, err := core.NewClient(
|
||||
addr, option.Protocol, auth, tlsConfig, quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl {
|
||||
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -16,16 +15,11 @@ import (
|
||||
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
|
||||
"github.com/sagernet/sing-shadowsocks"
|
||||
"github.com/sagernet/sing-shadowsocks/shadowimpl"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/uot"
|
||||
)
|
||||
|
||||
func init() {
|
||||
buf.DefaultAllocator = pool.DefaultAllocator
|
||||
}
|
||||
|
||||
type ShadowSocks struct {
|
||||
*Base
|
||||
method shadowsocks.Method
|
||||
@ -82,8 +76,7 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
|
||||
}
|
||||
}
|
||||
if metadata.NetWork == C.UDP && ss.option.UDPOverTCP {
|
||||
metadata.Host = uot.UOTMagicAddress
|
||||
metadata.DstPort = "443"
|
||||
return ss.method.DialConn(c, M.ParseSocksaddr(uot.UOTMagicAddress+":443"))
|
||||
}
|
||||
return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
}
|
||||
|
@ -52,6 +52,9 @@ type VmessOption struct {
|
||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
||||
XUDP bool `proxy:"xudp,omitempty"`
|
||||
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
||||
GlobalPadding bool `proxy:"global-padding,omitempty"`
|
||||
AuthenticatedLength bool `proxy:"authenticated-length,omitempty"`
|
||||
}
|
||||
|
||||
@ -197,7 +200,11 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
return nil, err
|
||||
}
|
||||
if metadata.NetWork == C.UDP {
|
||||
return v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
if v.option.XUDP {
|
||||
return v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
} else {
|
||||
return v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
}
|
||||
} else {
|
||||
return v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
}
|
||||
@ -244,6 +251,8 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
||||
}
|
||||
|
||||
if v.option.PacketAddr {
|
||||
_metadata := *metadata // make a copy
|
||||
metadata = &_metadata
|
||||
metadata.Host = packetaddr.SeqPacketMagicAddress
|
||||
metadata.DstPort = "443"
|
||||
}
|
||||
@ -257,7 +266,11 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
||||
}
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
if v.option.XUDP {
|
||||
c, err = v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
} else {
|
||||
c, err = v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
}
|
||||
} else {
|
||||
c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
|
||||
if err != nil {
|
||||
@ -274,7 +287,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
||||
}
|
||||
|
||||
if v.option.PacketAddr {
|
||||
return newPacketConn(&threadSafePacketConn{PacketConn: packetaddr.NewBindClient(c)}, v), nil
|
||||
return newPacketConn(&threadSafePacketConn{PacketConn: packetaddr.NewBindConn(c)}, v), nil
|
||||
} else if pc, ok := c.(net.PacketConn); ok {
|
||||
return newPacketConn(&threadSafePacketConn{PacketConn: pc}, v), nil
|
||||
}
|
||||
@ -284,7 +297,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
||||
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||
func (v *Vmess) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
if v.option.PacketAddr {
|
||||
return newPacketConn(&threadSafePacketConn{PacketConn: packetaddr.NewBindClient(c)}, v), nil
|
||||
return newPacketConn(&threadSafePacketConn{PacketConn: packetaddr.NewBindConn(c)}, v), nil
|
||||
} else if pc, ok := c.(net.PacketConn); ok {
|
||||
return newPacketConn(&threadSafePacketConn{PacketConn: pc}, v), nil
|
||||
}
|
||||
@ -299,6 +312,9 @@ func (v *Vmess) SupportUOT() bool {
|
||||
func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
security := strings.ToLower(option.Cipher)
|
||||
var options []vmess.ClientOption
|
||||
if option.GlobalPadding {
|
||||
options = append(options, vmess.ClientWithGlobalPadding())
|
||||
}
|
||||
if option.AuthenticatedLength {
|
||||
options = append(options, vmess.ClientWithAuthenticatedLength())
|
||||
}
|
||||
@ -307,6 +323,16 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch option.PacketEncoding {
|
||||
case "packetaddr", "packet":
|
||||
option.PacketAddr = true
|
||||
case "xudp":
|
||||
option.XUDP = true
|
||||
}
|
||||
if option.XUDP {
|
||||
option.PacketAddr = false
|
||||
}
|
||||
|
||||
switch option.Network {
|
||||
case "h2", "grpc":
|
||||
if !option.TLS {
|
||||
|
@ -31,7 +31,7 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts .
|
||||
c.AppendToChains(f)
|
||||
f.onDialSuccess()
|
||||
} else {
|
||||
f.onDialFailed()
|
||||
f.onDialFailed(proxy.Type(), err)
|
||||
}
|
||||
|
||||
return c, err
|
||||
@ -72,8 +72,8 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
// Unwrap implements C.ProxyAdapter
|
||||
func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
proxy := f.findAliveProxy(true)
|
||||
func (f *Fallback) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
|
||||
proxy := f.findAliveProxy(touch)
|
||||
return proxy
|
||||
}
|
||||
|
||||
|
@ -11,13 +11,14 @@ import (
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
"github.com/dlclark/regexp2"
|
||||
"go.uber.org/atomic"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type GroupBase struct {
|
||||
*outbound.Base
|
||||
filter *regexp2.Regexp
|
||||
filterRegs []*regexp2.Regexp
|
||||
providers []provider.ProxyProvider
|
||||
failedTestMux sync.Mutex
|
||||
failedTimes int
|
||||
@ -34,14 +35,17 @@ type GroupBaseOption struct {
|
||||
}
|
||||
|
||||
func NewGroupBase(opt GroupBaseOption) *GroupBase {
|
||||
var filter *regexp2.Regexp = nil
|
||||
var filterRegs []*regexp2.Regexp
|
||||
if opt.filter != "" {
|
||||
filter = regexp2.MustCompile(opt.filter, 0)
|
||||
for _, filter := range strings.Split(opt.filter, "`") {
|
||||
filterReg := regexp2.MustCompile(filter, 0)
|
||||
filterRegs = append(filterRegs, filterReg)
|
||||
}
|
||||
}
|
||||
|
||||
gb := &GroupBase{
|
||||
Base: outbound.NewBase(opt.BaseOption),
|
||||
filter: filter,
|
||||
filterRegs: filterRegs,
|
||||
providers: opt.providers,
|
||||
failedTesting: atomic.NewBool(false),
|
||||
}
|
||||
@ -52,8 +56,14 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase {
|
||||
return gb
|
||||
}
|
||||
|
||||
func (gb *GroupBase) Touch() {
|
||||
for _, pd := range gb.providers {
|
||||
pd.Touch()
|
||||
}
|
||||
}
|
||||
|
||||
func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
||||
if gb.filter == nil {
|
||||
if len(gb.filterRegs) == 0 {
|
||||
var proxies []C.Proxy
|
||||
for _, pd := range gb.providers {
|
||||
if touch {
|
||||
@ -79,16 +89,23 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
||||
}
|
||||
|
||||
version := gb.versions[i].Load()
|
||||
if version != pd.Version() && gb.versions[i].CAS(version, pd.Version()) {
|
||||
if version != pd.Version() && gb.versions[i].CompareAndSwap(version, pd.Version()) {
|
||||
var (
|
||||
proxies []C.Proxy
|
||||
newProxies []C.Proxy
|
||||
)
|
||||
|
||||
proxies = pd.Proxies()
|
||||
for _, p := range proxies {
|
||||
if mat, _ := gb.filter.FindStringMatch(p.Name()); mat != nil {
|
||||
newProxies = append(newProxies, p)
|
||||
proxiesSet := map[string]struct{}{}
|
||||
for _, filterReg := range gb.filterRegs {
|
||||
for _, p := range proxies {
|
||||
name := p.Name()
|
||||
if mat, _ := filterReg.FindStringMatch(name); mat != nil {
|
||||
if _, ok := proxiesSet[name]; !ok {
|
||||
proxiesSet[name] = struct{}{}
|
||||
newProxies = append(newProxies, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,6 +122,30 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
||||
return append(proxies, tunnel.Proxies()["COMPATIBLE"])
|
||||
}
|
||||
|
||||
if len(gb.providers) > 1 && len(gb.filterRegs) > 1 {
|
||||
var newProxies []C.Proxy
|
||||
proxiesSet := map[string]struct{}{}
|
||||
for _, filterReg := range gb.filterRegs {
|
||||
for _, p := range proxies {
|
||||
name := p.Name()
|
||||
if mat, _ := filterReg.FindStringMatch(name); mat != nil {
|
||||
if _, ok := proxiesSet[name]; !ok {
|
||||
proxiesSet[name] = struct{}{}
|
||||
newProxies = append(newProxies, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, p := range proxies { // add not matched proxies at the end
|
||||
name := p.Name()
|
||||
if _, ok := proxiesSet[name]; !ok {
|
||||
proxiesSet[name] = struct{}{}
|
||||
newProxies = append(newProxies, p)
|
||||
}
|
||||
}
|
||||
proxies = newProxies
|
||||
}
|
||||
|
||||
return proxies
|
||||
}
|
||||
|
||||
@ -136,8 +177,13 @@ func (gb *GroupBase) URLTest(ctx context.Context, url string) (map[string]uint16
|
||||
}
|
||||
}
|
||||
|
||||
func (gb *GroupBase) onDialFailed() {
|
||||
if gb.failedTesting.Load() {
|
||||
func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error) {
|
||||
if adapterType == C.Direct || adapterType == C.Compatible || adapterType == C.Reject || adapterType == C.Pass {
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
go gb.healthCheck()
|
||||
return
|
||||
}
|
||||
|
||||
@ -157,26 +203,34 @@ func (gb *GroupBase) onDialFailed() {
|
||||
|
||||
log.Debugln("ProxyGroup: %s failed count: %d", gb.Name(), gb.failedTimes)
|
||||
if gb.failedTimes >= gb.maxFailedTimes() {
|
||||
gb.failedTesting.Store(true)
|
||||
log.Warnln("because %s failed multiple times, active health check", gb.Name())
|
||||
wg := sync.WaitGroup{}
|
||||
for _, proxyProvider := range gb.providers {
|
||||
wg.Add(1)
|
||||
proxyProvider := proxyProvider
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
proxyProvider.HealthCheck()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
gb.failedTesting.Store(false)
|
||||
gb.failedTimes = 0
|
||||
gb.healthCheck()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (gb *GroupBase) healthCheck() {
|
||||
if gb.failedTesting.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
gb.failedTesting.Store(true)
|
||||
wg := sync.WaitGroup{}
|
||||
for _, proxyProvider := range gb.providers {
|
||||
wg.Add(1)
|
||||
proxyProvider := proxyProvider
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
proxyProvider.HealthCheck()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
gb.failedTesting.Store(false)
|
||||
gb.failedTimes = 0
|
||||
}
|
||||
|
||||
func (gb *GroupBase) failedIntervalTime() int64 {
|
||||
return 5 * time.Second.Milliseconds()
|
||||
}
|
||||
|
@ -82,17 +82,17 @@ func jumpHash(key uint64, buckets int32) int32 {
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) {
|
||||
proxy := lb.Unwrap(metadata, true)
|
||||
|
||||
defer func() {
|
||||
if err == nil {
|
||||
c.AppendToChains(lb)
|
||||
lb.onDialSuccess()
|
||||
} else {
|
||||
lb.onDialFailed()
|
||||
lb.onDialFailed(proxy.Type(), err)
|
||||
}
|
||||
}()
|
||||
|
||||
proxy := lb.Unwrap(metadata)
|
||||
|
||||
c, err = proxy.DialContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
|
||||
return
|
||||
}
|
||||
@ -105,7 +105,7 @@ func (lb *LoadBalance) ListenPacketContext(ctx context.Context, metadata *C.Meta
|
||||
}
|
||||
}()
|
||||
|
||||
proxy := lb.Unwrap(metadata)
|
||||
proxy := lb.Unwrap(metadata, true)
|
||||
return proxy.ListenPacketContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
|
||||
}
|
||||
|
||||
@ -190,8 +190,8 @@ func strategyStickySessions() strategyFn {
|
||||
}
|
||||
|
||||
// Unwrap implements C.ProxyAdapter
|
||||
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
proxies := lb.GetProxies(true)
|
||||
func (lb *LoadBalance) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
|
||||
proxies := lb.GetProxies(touch)
|
||||
return lb.strategyFn(proxies, metadata)
|
||||
}
|
||||
|
||||
|
@ -153,11 +153,11 @@ func (r *Relay) proxies(metadata *C.Metadata, touch bool) ([]C.Proxy, []C.Proxy)
|
||||
for n, proxy := range rawProxies {
|
||||
proxies = append(proxies, proxy)
|
||||
chainProxies = append(chainProxies, proxy)
|
||||
subproxy := proxy.Unwrap(metadata)
|
||||
subproxy := proxy.Unwrap(metadata, touch)
|
||||
for subproxy != nil {
|
||||
chainProxies = append(chainProxies, subproxy)
|
||||
proxies[n] = subproxy
|
||||
subproxy = subproxy.Unwrap(metadata)
|
||||
subproxy = subproxy.Unwrap(metadata, touch)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,8 +74,8 @@ func (s *Selector) Set(name string) error {
|
||||
}
|
||||
|
||||
// Unwrap implements C.ProxyAdapter
|
||||
func (s *Selector) Unwrap(*C.Metadata) C.Proxy {
|
||||
return s.selectedProxy(true)
|
||||
func (s *Selector) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
|
||||
return s.selectedProxy(touch)
|
||||
}
|
||||
|
||||
func (s *Selector) selectedProxy(touch bool) C.Proxy {
|
||||
|
@ -34,12 +34,13 @@ func (u *URLTest) Now() string {
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) {
|
||||
c, err = u.fast(true).DialContext(ctx, metadata, u.Base.DialOptions(opts...)...)
|
||||
proxy := u.fast(true)
|
||||
c, err = proxy.DialContext(ctx, metadata, u.Base.DialOptions(opts...)...)
|
||||
if err == nil {
|
||||
c.AppendToChains(u)
|
||||
u.onDialSuccess()
|
||||
} else {
|
||||
u.onDialFailed()
|
||||
u.onDialFailed(proxy.Type(), err)
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
@ -55,12 +56,12 @@ func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
}
|
||||
|
||||
// Unwrap implements C.ProxyAdapter
|
||||
func (u *URLTest) Unwrap(*C.Metadata) C.Proxy {
|
||||
return u.fast(true)
|
||||
func (u *URLTest) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
|
||||
return u.fast(touch)
|
||||
}
|
||||
|
||||
func (u *URLTest) fast(touch bool) C.Proxy {
|
||||
elm, _, _ := u.fastSingle.Do(func() (C.Proxy, error) {
|
||||
elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) {
|
||||
proxies := u.GetProxies(touch)
|
||||
fast := proxies[0]
|
||||
min := fast.LastDelay()
|
||||
@ -89,6 +90,9 @@ func (u *URLTest) fast(touch bool) C.Proxy {
|
||||
|
||||
return u.fastNode, nil
|
||||
})
|
||||
if shared && touch { // a shared fastSingle.Do() may cause providers untouched, so we touch them again
|
||||
u.Touch()
|
||||
}
|
||||
|
||||
return elm
|
||||
}
|
||||
|
@ -2,12 +2,14 @@ package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/batch"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
@ -35,16 +37,13 @@ func (hc *HealthCheck) process() {
|
||||
|
||||
go func() {
|
||||
time.Sleep(30 * time.Second)
|
||||
hc.check()
|
||||
hc.lazyCheck()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
now := time.Now().Unix()
|
||||
if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) {
|
||||
hc.check()
|
||||
}
|
||||
hc.lazyCheck()
|
||||
case <-hc.done:
|
||||
ticker.Stop()
|
||||
return
|
||||
@ -52,6 +51,17 @@ func (hc *HealthCheck) process() {
|
||||
}
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) lazyCheck() bool {
|
||||
now := time.Now().Unix()
|
||||
if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) {
|
||||
hc.check()
|
||||
return true
|
||||
} else {
|
||||
log.Debugln("Skip once health check because we are lazy")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) setProxy(proxies []C.Proxy) {
|
||||
hc.proxies = proxies
|
||||
}
|
||||
@ -66,18 +76,26 @@ func (hc *HealthCheck) touch() {
|
||||
|
||||
func (hc *HealthCheck) check() {
|
||||
_, _, _ = hc.singleDo.Do(func() (struct{}, error) {
|
||||
id := ""
|
||||
if uid, err := uuid.NewV4(); err == nil {
|
||||
id = uid.String()
|
||||
}
|
||||
log.Debugln("Start New Health Checking {%s}", id)
|
||||
b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10))
|
||||
for _, proxy := range hc.proxies {
|
||||
p := proxy
|
||||
b.Go(p.Name(), func() (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||
defer cancel()
|
||||
log.Debugln("Health Checking %s {%s}", p.Name(), id)
|
||||
_, _ = p.URLTest(ctx, hc.url)
|
||||
log.Debugln("Health Checked %s : %t %d ms {%s}", p.Name(), p.Alive(), p.LastDelay(), id)
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
||||
b.Wait()
|
||||
log.Debugln("Finish A Health Checking {%s}", id)
|
||||
return struct{}{}, nil
|
||||
})
|
||||
}
|
||||
|
@ -21,12 +21,13 @@ type healthCheckSchema struct {
|
||||
}
|
||||
|
||||
type proxyProviderSchema struct {
|
||||
Type string `provider:"type"`
|
||||
Path string `provider:"path"`
|
||||
URL string `provider:"url,omitempty"`
|
||||
Interval int `provider:"interval,omitempty"`
|
||||
Filter string `provider:"filter,omitempty"`
|
||||
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
||||
Type string `provider:"type"`
|
||||
Path string `provider:"path"`
|
||||
URL string `provider:"url,omitempty"`
|
||||
Interval int `provider:"interval,omitempty"`
|
||||
Filter string `provider:"filter,omitempty"`
|
||||
ExcludeFilter string `provider:"exclude-filter,omitempty"`
|
||||
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
||||
}
|
||||
|
||||
func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) {
|
||||
@ -61,5 +62,6 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
||||
|
||||
interval := time.Duration(uint(schema.Interval)) * time.Second
|
||||
filter := schema.Filter
|
||||
return NewProxySetProvider(name, interval, filter, vehicle, hc)
|
||||
excludeFilter := schema.ExcludeFilter
|
||||
return NewProxySetProvider(name, interval, filter, excludeFilter, vehicle, hc)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/Dreamacro/clash/component/resource"
|
||||
"github.com/dlclark/regexp2"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
@ -92,7 +93,7 @@ func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
|
||||
pp.proxies = proxies
|
||||
pp.healthCheck.setProxy(proxies)
|
||||
if pp.healthCheck.auto() {
|
||||
defer func() { go pp.healthCheck.check() }()
|
||||
defer func() { go pp.healthCheck.lazyCheck() }()
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,10 +102,18 @@ func stopProxyProvider(pd *ProxySetProvider) {
|
||||
_ = pd.Fetcher.Destroy()
|
||||
}
|
||||
|
||||
func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
|
||||
filterReg, err := regexp2.Compile(filter, 0)
|
||||
func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
|
||||
excludeFilterReg, err := regexp2.Compile(excludeFilter, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid filter regex: %w", err)
|
||||
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
|
||||
}
|
||||
var filterRegs []*regexp2.Regexp
|
||||
for _, filter := range strings.Split(filter, "`") {
|
||||
filterReg, err := regexp2.Compile(filter, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid filter regex: %w", err)
|
||||
}
|
||||
filterRegs = append(filterRegs, filterReg)
|
||||
}
|
||||
|
||||
if hc.auto() {
|
||||
@ -116,7 +125,7 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh
|
||||
healthCheck: hc,
|
||||
}
|
||||
|
||||
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, filterReg), proxiesOnUpdate(pd))
|
||||
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, filterRegs, excludeFilterReg), proxiesOnUpdate(pd))
|
||||
pd.Fetcher = fetcher
|
||||
|
||||
wrapper := &ProxySetProvider{pd}
|
||||
@ -212,7 +221,7 @@ func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
|
||||
}
|
||||
}
|
||||
|
||||
func proxiesParseAndFilter(filter string, filterReg *regexp2.Regexp) resource.Parser[[]C.Proxy] {
|
||||
func proxiesParseAndFilter(filter string, excludeFilter string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp) resource.Parser[[]C.Proxy] {
|
||||
return func(buf []byte) ([]C.Proxy, error) {
|
||||
schema := &ProxySchema{}
|
||||
|
||||
@ -229,17 +238,37 @@ func proxiesParseAndFilter(filter string, filterReg *regexp2.Regexp) resource.Pa
|
||||
}
|
||||
|
||||
proxies := []C.Proxy{}
|
||||
for idx, mapping := range schema.Proxies {
|
||||
name, ok := mapping["name"]
|
||||
mat, _ := filterReg.FindStringMatch(name.(string))
|
||||
if ok && len(filter) > 0 && mat == nil {
|
||||
continue
|
||||
proxiesSet := map[string]struct{}{}
|
||||
for _, filterReg := range filterRegs {
|
||||
for idx, mapping := range schema.Proxies {
|
||||
mName, ok := mapping["name"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
name, ok := mName.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if len(excludeFilter) > 0 {
|
||||
if mat, _ := excludeFilterReg.FindStringMatch(name); mat != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(filter) > 0 {
|
||||
if mat, _ := filterReg.FindStringMatch(name); mat == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if _, ok := proxiesSet[name]; ok {
|
||||
continue
|
||||
}
|
||||
proxy, err := adapter.ParseProxy(mapping)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
|
||||
}
|
||||
proxiesSet[name] = struct{}{}
|
||||
proxies = append(proxies, proxy)
|
||||
}
|
||||
proxy, err := adapter.ParseProxy(mapping)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
|
||||
}
|
||||
proxies = append(proxies, proxy)
|
||||
}
|
||||
|
||||
if len(proxies) == 0 {
|
||||
|
@ -47,7 +47,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
hysteria["port"] = urlHysteria.Port()
|
||||
hysteria["sni"] = query.Get("peer")
|
||||
hysteria["obfs"] = query.Get("obfs")
|
||||
hysteria["alpn"] = query.Get("alpn")
|
||||
hysteria["alpn"] = []string{query.Get("alpn")}
|
||||
hysteria["auth_str"] = query.Get("auth")
|
||||
hysteria["protocol"] = query.Get("protocol")
|
||||
up := query.Get("up")
|
||||
@ -144,6 +144,14 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
if encryption := query.Get("encryption"); encryption != "" {
|
||||
vmess["cipher"] = encryption
|
||||
}
|
||||
if packetEncoding := query.Get("packetEncoding"); packetEncoding != "" {
|
||||
switch packetEncoding {
|
||||
case "packet":
|
||||
vmess["packet-addr"] = true
|
||||
case "xudp":
|
||||
vmess["xudp"] = true
|
||||
}
|
||||
}
|
||||
proxies = append(proxies, vmess)
|
||||
continue
|
||||
}
|
||||
|
@ -4,8 +4,6 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
)
|
||||
|
||||
// Relay copies between left and right bidirectionally.
|
||||
@ -13,18 +11,14 @@ func Relay(leftConn, rightConn net.Conn) {
|
||||
ch := make(chan error)
|
||||
|
||||
go func() {
|
||||
buf := pool.Get(pool.RelayBufferSize)
|
||||
// Wrapping to avoid using *net.TCPConn.(ReadFrom)
|
||||
// See also https://github.com/Dreamacro/clash/pull/1209
|
||||
_, err := io.CopyBuffer(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn}, buf)
|
||||
pool.Put(buf)
|
||||
_, err := io.Copy(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn})
|
||||
leftConn.SetReadDeadline(time.Now())
|
||||
ch <- err
|
||||
}()
|
||||
|
||||
buf := pool.Get(pool.RelayBufferSize)
|
||||
io.CopyBuffer(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn}, buf)
|
||||
pool.Put(buf)
|
||||
_, _ = io.Copy(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn})
|
||||
rightConn.SetReadDeadline(time.Now())
|
||||
<-ch
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var DefaultAllocator = NewAllocator()
|
||||
var defaultAllocator = NewAllocator()
|
||||
|
||||
// Allocator for incoming frames, optimized to prevent overwriting after zeroing
|
||||
type Allocator struct {
|
||||
|
@ -13,9 +13,9 @@ const (
|
||||
)
|
||||
|
||||
func Get(size int) []byte {
|
||||
return DefaultAllocator.Get(size)
|
||||
return defaultAllocator.Get(size)
|
||||
}
|
||||
|
||||
func Put(buf []byte) error {
|
||||
return DefaultAllocator.Put(buf)
|
||||
return defaultAllocator.Put(buf)
|
||||
}
|
||||
|
7
common/pool/sing.go
Normal file
7
common/pool/sing.go
Normal file
@ -0,0 +1,7 @@
|
||||
package pool
|
||||
|
||||
import "github.com/sagernet/sing/common/buf"
|
||||
|
||||
func init() {
|
||||
buf.DefaultAllocator = defaultAllocator
|
||||
}
|
@ -290,6 +290,7 @@ func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr
|
||||
|
||||
connCount := len(ips)
|
||||
var fallback dialResult
|
||||
var primaryError error
|
||||
for i := 0; i < connCount; i++ {
|
||||
select {
|
||||
case res := <-results:
|
||||
@ -303,6 +304,7 @@ func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr
|
||||
}
|
||||
} else {
|
||||
if res.isPrimary {
|
||||
primaryError = res.error
|
||||
preferCount.Add(-1)
|
||||
if preferCount.Load() == 0 && fallback.done && fallback.error == nil {
|
||||
return fallback.Conn, nil
|
||||
@ -321,6 +323,14 @@ func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr
|
||||
return fallback.Conn, nil
|
||||
}
|
||||
|
||||
if primaryError != nil {
|
||||
return nil, primaryError
|
||||
}
|
||||
|
||||
if fallback.error != nil {
|
||||
return nil, fallback.error
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("all ips %v tcp shake hands failed", ips)
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,30 @@ import (
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
func GetAutoDetectInterface() (string, error) {
|
||||
routes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
if route.Dst == nil {
|
||||
lk, err := netlink.LinkByIndex(route.LinkIndex)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if lk.Type() == "tuntap" {
|
||||
continue
|
||||
}
|
||||
|
||||
return lk.Attrs().Name, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("interface not found")
|
||||
}
|
||||
|
||||
// NewTcEBpfProgram new redirect to tun ebpf program
|
||||
func NewTcEBpfProgram(ifaceNames []string, tunName string) (*TcEBpfProgram, error) {
|
||||
tunIface, err := netlink.LinkByName(tunName)
|
||||
|
@ -15,3 +15,7 @@ func NewTcEBpfProgram(_ []string, _ string) (*TcEBpfProgram, error) {
|
||||
func NewRedirEBpfProgram(_ []string, _ uint16, _ string) (*TcEBpfProgram, error) {
|
||||
return nil, fmt.Errorf("system not supported")
|
||||
}
|
||||
|
||||
func GetAutoDetectInterface() (string, error) {
|
||||
return "", fmt.Errorf("system not supported")
|
||||
}
|
||||
|
@ -166,7 +166,7 @@ func New(options Options) (*Pool, error) {
|
||||
var (
|
||||
hostAddr = options.IPNet.Masked().Addr()
|
||||
gateway = hostAddr.Next()
|
||||
first = gateway.Next().Next()
|
||||
first = gateway.Next().Next().Next() // default start with 198.18.0.4
|
||||
last = nnip.UnMasked(*options.IPNet)
|
||||
)
|
||||
|
||||
|
@ -62,16 +62,16 @@ func TestPool_Basic(t *testing.T) {
|
||||
last := pool.Lookup("bar.com")
|
||||
bar, exist := pool.LookBack(last)
|
||||
|
||||
assert.True(t, first == netip.AddrFrom4([4]byte{192, 168, 0, 3}))
|
||||
assert.True(t, pool.Lookup("foo.com") == netip.AddrFrom4([4]byte{192, 168, 0, 3}))
|
||||
assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 4}))
|
||||
assert.True(t, first == netip.AddrFrom4([4]byte{192, 168, 0, 4}))
|
||||
assert.True(t, pool.Lookup("foo.com") == netip.AddrFrom4([4]byte{192, 168, 0, 4}))
|
||||
assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 5}))
|
||||
assert.True(t, exist)
|
||||
assert.Equal(t, bar, "bar.com")
|
||||
assert.True(t, pool.Gateway() == netip.AddrFrom4([4]byte{192, 168, 0, 1}))
|
||||
assert.True(t, pool.Broadcast() == netip.AddrFrom4([4]byte{192, 168, 0, 15}))
|
||||
assert.Equal(t, pool.IPNet().String(), ipnet.String())
|
||||
assert.True(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 4})))
|
||||
assert.False(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 5})))
|
||||
assert.True(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 5})))
|
||||
assert.False(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 6})))
|
||||
assert.False(t, pool.Exist(netip.MustParseAddr("::1")))
|
||||
}
|
||||
}
|
||||
@ -90,16 +90,16 @@ func TestPool_BasicV6(t *testing.T) {
|
||||
last := pool.Lookup("bar.com")
|
||||
bar, exist := pool.LookBack(last)
|
||||
|
||||
assert.True(t, first == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8803"))
|
||||
assert.True(t, pool.Lookup("foo.com") == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8803"))
|
||||
assert.True(t, last == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804"))
|
||||
assert.True(t, first == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804"))
|
||||
assert.True(t, pool.Lookup("foo.com") == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804"))
|
||||
assert.True(t, last == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8805"))
|
||||
assert.True(t, exist)
|
||||
assert.Equal(t, bar, "bar.com")
|
||||
assert.True(t, pool.Gateway() == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8801"))
|
||||
assert.True(t, pool.Broadcast() == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8bff"))
|
||||
assert.Equal(t, pool.IPNet().String(), ipnet.String())
|
||||
assert.True(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804")))
|
||||
assert.False(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8805")))
|
||||
assert.True(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8805")))
|
||||
assert.False(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8806")))
|
||||
assert.False(t, pool.Exist(netip.MustParseAddr("127.0.0.1")))
|
||||
}
|
||||
}
|
||||
@ -116,7 +116,7 @@ func TestPool_CycleUsed(t *testing.T) {
|
||||
for _, pool := range pools {
|
||||
foo := pool.Lookup("foo.com")
|
||||
bar := pool.Lookup("bar.com")
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := 0; i < 9; i++ {
|
||||
pool.Lookup(fmt.Sprintf("%d.com", i))
|
||||
}
|
||||
baz := pool.Lookup("baz.com")
|
||||
@ -198,8 +198,8 @@ func TestPool_Clone(t *testing.T) {
|
||||
|
||||
first := pool.Lookup("foo.com")
|
||||
last := pool.Lookup("bar.com")
|
||||
assert.True(t, first == netip.AddrFrom4([4]byte{192, 168, 0, 3}))
|
||||
assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 4}))
|
||||
assert.True(t, first == netip.AddrFrom4([4]byte{192, 168, 0, 4}))
|
||||
assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 5}))
|
||||
|
||||
newPool, _ := New(Options{
|
||||
IPNet: &ipnet,
|
||||
|
@ -3,6 +3,8 @@ package process
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
@ -15,6 +17,22 @@ const (
|
||||
proccallnumpidinfo = 0x2
|
||||
)
|
||||
|
||||
var structSize = func() int {
|
||||
value, _ := syscall.Sysctl("kern.osrelease")
|
||||
major, _, _ := strings.Cut(value, ".")
|
||||
n, _ := strconv.ParseInt(major, 10, 64)
|
||||
switch true {
|
||||
case n >= 22:
|
||||
return 408
|
||||
default:
|
||||
// from darwin-xnu/bsd/netinet/in_pcblist.c:get_pcblist_n
|
||||
// size/offset are round up (aligned) to 8 bytes in darwin
|
||||
// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
|
||||
// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
|
||||
return 384
|
||||
}
|
||||
}()
|
||||
|
||||
func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, int32, error) {
|
||||
return 0, 0, ErrPlatformNotSupport
|
||||
}
|
||||
@ -38,12 +56,7 @@ func findProcessName(network string, ip netip.Addr, port int) (int32, string, er
|
||||
}
|
||||
|
||||
buf := []byte(value)
|
||||
|
||||
// from darwin-xnu/bsd/netinet/in_pcblist.c:get_pcblist_n
|
||||
// size/offset are round up (aligned) to 8 bytes in darwin
|
||||
// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
|
||||
// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
|
||||
itemSize := 384
|
||||
itemSize := structSize
|
||||
if network == TCP {
|
||||
// rup8(sizeof(xtcpcb_n))
|
||||
itemSize += 208
|
||||
|
@ -2,17 +2,19 @@ package sniffer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/Dreamacro/clash/constant/sniffer"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
|
||||
CN "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/sniffer"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
@ -22,27 +24,30 @@ var (
|
||||
ErrNoClue = errors.New("not enough information for making a decision")
|
||||
)
|
||||
|
||||
var Dispatcher SnifferDispatcher
|
||||
var Dispatcher *SnifferDispatcher
|
||||
|
||||
type (
|
||||
SnifferDispatcher struct {
|
||||
enable bool
|
||||
type SnifferDispatcher struct {
|
||||
enable bool
|
||||
|
||||
sniffers []sniffer.Sniffer
|
||||
sniffers []sniffer.Sniffer
|
||||
|
||||
foreDomain *trie.DomainTrie[bool]
|
||||
skipSNI *trie.DomainTrie[bool]
|
||||
portRanges *[]utils.Range[uint16]
|
||||
}
|
||||
)
|
||||
forceDomain *trie.DomainTrie[bool]
|
||||
skipSNI *trie.DomainTrie[bool]
|
||||
portRanges *[]utils.Range[uint16]
|
||||
skipList *cache.LruCache[string, uint8]
|
||||
rwMux sync.RWMutex
|
||||
|
||||
forceDnsMapping bool
|
||||
parsePureIp bool
|
||||
}
|
||||
|
||||
func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) {
|
||||
bufConn, ok := conn.(*CN.BufferedConn)
|
||||
bufConn, ok := conn.(*N.BufferedConn)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if metadata.Host == "" || sd.foreDomain.Search(metadata.Host) != nil {
|
||||
if (metadata.Host == "" && sd.parsePureIp) || sd.forceDomain.Search(metadata.Host) != nil || (metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) {
|
||||
port, err := strconv.ParseUint(metadata.DstPort, 10, 16)
|
||||
if err != nil {
|
||||
log.Debugln("[Sniffer] Dst port is error")
|
||||
@ -61,7 +66,17 @@ func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) {
|
||||
return
|
||||
}
|
||||
|
||||
sd.rwMux.RLock()
|
||||
dst := fmt.Sprintf("%s:%s", metadata.DstIP, metadata.DstPort)
|
||||
if count, ok := sd.skipList.Get(dst); ok && count > 5 {
|
||||
log.Debugln("[Sniffer] Skip sniffing[%s] due to multiple failures", dst)
|
||||
defer sd.rwMux.RUnlock()
|
||||
return
|
||||
}
|
||||
sd.rwMux.RUnlock()
|
||||
|
||||
if host, err := sd.sniffDomain(bufConn, metadata); err != nil {
|
||||
sd.cacheSniffFailed(metadata)
|
||||
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%s] to [%s:%s]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
|
||||
return
|
||||
} else {
|
||||
@ -70,16 +85,32 @@ func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) {
|
||||
return
|
||||
}
|
||||
|
||||
sd.rwMux.RLock()
|
||||
sd.skipList.Delete(dst)
|
||||
sd.rwMux.RUnlock()
|
||||
|
||||
sd.replaceDomain(metadata, host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string) {
|
||||
log.Debugln("[Sniffer] Sniff TCP [%s:%s]-->[%s:%s] success, replace domain [%s]-->[%s]",
|
||||
metadata.SrcIP, metadata.SrcPort,
|
||||
metadata.DstIP, metadata.DstPort,
|
||||
metadata.Host, host)
|
||||
dstIP := ""
|
||||
if metadata.DstIP.IsValid() {
|
||||
dstIP = metadata.DstIP.String()
|
||||
}
|
||||
originHost := metadata.Host
|
||||
if originHost != host {
|
||||
log.Infoln("[Sniffer] Sniff TCP [%s:%s]-->[%s:%s] success, replace domain [%s]-->[%s]",
|
||||
metadata.SrcIP, metadata.SrcPort,
|
||||
dstIP, metadata.DstPort,
|
||||
metadata.Host, host)
|
||||
} else {
|
||||
log.Debugln("[Sniffer] Sniff TCP [%s:%s]-->[%s:%s] success, replace domain [%s]-->[%s]",
|
||||
metadata.SrcIP, metadata.SrcPort,
|
||||
dstIP, metadata.DstPort,
|
||||
metadata.Host, host)
|
||||
}
|
||||
|
||||
metadata.AddrType = C.AtypDomainName
|
||||
metadata.Host = host
|
||||
@ -90,15 +121,16 @@ func (sd *SnifferDispatcher) Enable() bool {
|
||||
return sd.enable
|
||||
}
|
||||
|
||||
func (sd *SnifferDispatcher) sniffDomain(conn *CN.BufferedConn, metadata *C.Metadata) (string, error) {
|
||||
for _, sniffer := range sd.sniffers {
|
||||
if sniffer.SupportNetwork() == C.TCP {
|
||||
_ = conn.SetReadDeadline(time.Now().Add(3 * time.Second))
|
||||
func (sd *SnifferDispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metadata) (string, error) {
|
||||
for _, s := range sd.sniffers {
|
||||
if s.SupportNetwork() == C.TCP {
|
||||
_ = conn.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||
_, err := conn.Peek(1)
|
||||
_ = conn.SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
_, ok := err.(*net.OpError)
|
||||
if ok {
|
||||
sd.cacheSniffFailed(metadata)
|
||||
log.Errorln("[Sniffer] [%s] may not have any sent data, Consider adding skip", metadata.DstIP.String())
|
||||
_ = conn.Close()
|
||||
}
|
||||
@ -113,15 +145,15 @@ func (sd *SnifferDispatcher) sniffDomain(conn *CN.BufferedConn, metadata *C.Meta
|
||||
continue
|
||||
}
|
||||
|
||||
host, err := sniffer.SniffTCP(bytes)
|
||||
host, err := s.SniffTCP(bytes)
|
||||
if err != nil {
|
||||
//log.Debugln("[Sniffer] [%s] Sniff data failed %s", sniffer.Protocol(), metadata.DstIP)
|
||||
//log.Debugln("[Sniffer] [%s] Sniff data failed %s", s.Protocol(), metadata.DstIP)
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = netip.ParseAddr(host)
|
||||
if err == nil {
|
||||
//log.Debugln("[Sniffer] [%s] Sniff data failed %s", sniffer.Protocol(), metadata.DstIP)
|
||||
//log.Debugln("[Sniffer] [%s] Sniff data failed %s", s.Protocol(), metadata.DstIP)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -132,6 +164,17 @@ func (sd *SnifferDispatcher) sniffDomain(conn *CN.BufferedConn, metadata *C.Meta
|
||||
return "", ErrorSniffFailed
|
||||
}
|
||||
|
||||
func (sd *SnifferDispatcher) cacheSniffFailed(metadata *C.Metadata) {
|
||||
sd.rwMux.Lock()
|
||||
dst := fmt.Sprintf("%s:%s", metadata.DstIP, metadata.DstPort)
|
||||
count, _ := sd.skipList.Get(dst)
|
||||
if count <= 5 {
|
||||
count++
|
||||
}
|
||||
sd.skipList.Set(dst, count)
|
||||
sd.rwMux.Unlock()
|
||||
}
|
||||
|
||||
func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) {
|
||||
dispatcher := SnifferDispatcher{
|
||||
enable: false,
|
||||
@ -141,22 +184,26 @@ func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) {
|
||||
}
|
||||
|
||||
func NewSnifferDispatcher(needSniffer []sniffer.Type, forceDomain *trie.DomainTrie[bool],
|
||||
skipSNI *trie.DomainTrie[bool], ports *[]utils.Range[uint16]) (*SnifferDispatcher, error) {
|
||||
skipSNI *trie.DomainTrie[bool], ports *[]utils.Range[uint16],
|
||||
forceDnsMapping bool, parsePureIp bool) (*SnifferDispatcher, error) {
|
||||
dispatcher := SnifferDispatcher{
|
||||
enable: true,
|
||||
foreDomain: forceDomain,
|
||||
skipSNI: skipSNI,
|
||||
portRanges: ports,
|
||||
enable: true,
|
||||
forceDomain: forceDomain,
|
||||
skipSNI: skipSNI,
|
||||
portRanges: ports,
|
||||
skipList: cache.NewLRUCache[string, uint8](cache.WithSize[string, uint8](128), cache.WithAge[string, uint8](600)),
|
||||
forceDnsMapping: forceDnsMapping,
|
||||
parsePureIp: parsePureIp,
|
||||
}
|
||||
|
||||
for _, snifferName := range needSniffer {
|
||||
sniffer, err := NewSniffer(snifferName)
|
||||
s, err := NewSniffer(snifferName)
|
||||
if err != nil {
|
||||
log.Errorln("Sniffer name[%s] is error", snifferName)
|
||||
return &SnifferDispatcher{enable: false}, err
|
||||
}
|
||||
|
||||
dispatcher.sniffers = append(dispatcher.sniffers, sniffer)
|
||||
dispatcher.sniffers = append(dispatcher.sniffers, s)
|
||||
}
|
||||
|
||||
return &dispatcher, nil
|
||||
|
@ -4,9 +4,10 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -12,10 +12,10 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var globalFingerprints [][32]byte
|
||||
var globalFingerprints = make([][32]byte, 0, 0)
|
||||
var mutex sync.Mutex
|
||||
|
||||
func verifyPeerCertificateAndFingerprints(fingerprints [][32]byte, insecureSkipVerify bool) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
func verifyPeerCertificateAndFingerprints(fingerprints *[][32]byte, insecureSkipVerify bool) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
if insecureSkipVerify {
|
||||
return nil
|
||||
@ -34,7 +34,7 @@ func verifyPeerCertificateAndFingerprints(fingerprints [][32]byte, insecureSkipV
|
||||
return nil
|
||||
} else {
|
||||
fingerprint := sha256.Sum256(cert.Raw)
|
||||
for _, fp := range fingerprints {
|
||||
for _, fp := range *fingerprints {
|
||||
if bytes.Equal(fingerprint[:], fp[:]) {
|
||||
return nil
|
||||
}
|
||||
@ -85,10 +85,10 @@ func GetSpecifiedFingerprintTLSConfig(tlsConfig *tls.Config, fingerprint string)
|
||||
if tlsConfig == nil {
|
||||
return &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
VerifyPeerCertificate: verifyPeerCertificateAndFingerprints([][32]byte{*fingerprintBytes}, false),
|
||||
VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, false),
|
||||
}, nil
|
||||
} else {
|
||||
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints([][32]byte{*fingerprintBytes}, tlsConfig.InsecureSkipVerify)
|
||||
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, tlsConfig.InsecureSkipVerify)
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
return tlsConfig, nil
|
||||
}
|
||||
@ -99,11 +99,11 @@ func GetGlobalFingerprintTLCConfig(tlsConfig *tls.Config) *tls.Config {
|
||||
if tlsConfig == nil {
|
||||
return &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(globalFingerprints, false),
|
||||
VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&globalFingerprints, false),
|
||||
}
|
||||
}
|
||||
|
||||
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(globalFingerprints, tlsConfig.InsecureSkipVerify)
|
||||
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&globalFingerprints, tlsConfig.InsecureSkipVerify)
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
return tlsConfig
|
||||
}
|
||||
@ -116,10 +116,10 @@ func GetSpecifiedFingerprintXTLSConfig(tlsConfig *xtls.Config, fingerprint strin
|
||||
if tlsConfig == nil {
|
||||
return &xtls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
VerifyPeerCertificate: verifyPeerCertificateAndFingerprints([][32]byte{*fingerprintBytes}, false),
|
||||
VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, false),
|
||||
}, nil
|
||||
} else {
|
||||
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints([][32]byte{*fingerprintBytes}, tlsConfig.InsecureSkipVerify)
|
||||
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, tlsConfig.InsecureSkipVerify)
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
return tlsConfig, nil
|
||||
}
|
||||
@ -130,11 +130,11 @@ func GetGlobalFingerprintXTLCConfig(tlsConfig *xtls.Config) *xtls.Config {
|
||||
if tlsConfig == nil {
|
||||
return &xtls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(globalFingerprints, false),
|
||||
VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&globalFingerprints, false),
|
||||
}
|
||||
}
|
||||
|
||||
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(globalFingerprints, tlsConfig.InsecureSkipVerify)
|
||||
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&globalFingerprints, tlsConfig.InsecureSkipVerify)
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
return tlsConfig
|
||||
}
|
||||
|
293
config/config.go
293
config/config.go
@ -2,10 +2,9 @@ package config
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/constant/sniffer"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
||||
"net"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
@ -31,6 +30,7 @@ import (
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
||||
"github.com/Dreamacro/clash/constant/sniffer"
|
||||
snifferTypes "github.com/Dreamacro/clash/constant/sniffer"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
@ -118,8 +118,73 @@ type Tun struct {
|
||||
DNSHijack []netip.AddrPort `yaml:"dns-hijack" json:"dns-hijack"`
|
||||
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
|
||||
AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"`
|
||||
TunAddressPrefix netip.Prefix `yaml:"-" json:"-"`
|
||||
RedirectToTun []string `yaml:"-" json:"-"`
|
||||
|
||||
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
||||
Inet4Address []ListenPrefix `yaml:"inet4-address" json:"inet4_address,omitempty"`
|
||||
Inet6Address []ListenPrefix `yaml:"inet6-address" json:"inet6_address,omitempty"`
|
||||
StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"`
|
||||
Inet4RouteAddress []ListenPrefix `yaml:"inet4_route_address" json:"inet4_route_address,omitempty"`
|
||||
Inet6RouteAddress []ListenPrefix `yaml:"inet6_route_address" json:"inet6_route_address,omitempty"`
|
||||
IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"`
|
||||
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
|
||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
|
||||
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"`
|
||||
IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"`
|
||||
IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"`
|
||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"`
|
||||
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"`
|
||||
UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"`
|
||||
}
|
||||
|
||||
type ListenPrefix netip.Prefix
|
||||
|
||||
func (p ListenPrefix) MarshalJSON() ([]byte, error) {
|
||||
prefix := netip.Prefix(p)
|
||||
if !prefix.IsValid() {
|
||||
return json.Marshal(nil)
|
||||
}
|
||||
return json.Marshal(prefix.String())
|
||||
}
|
||||
|
||||
func (p ListenPrefix) MarshalYAML() (interface{}, error) {
|
||||
prefix := netip.Prefix(p)
|
||||
if !prefix.IsValid() {
|
||||
return nil, nil
|
||||
}
|
||||
return prefix.String(), nil
|
||||
}
|
||||
|
||||
func (p *ListenPrefix) UnmarshalJSON(bytes []byte) error {
|
||||
var value string
|
||||
err := json.Unmarshal(bytes, &value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prefix, err := netip.ParsePrefix(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*p = ListenPrefix(prefix)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ListenPrefix) UnmarshalYAML(node *yaml.Node) error {
|
||||
var value string
|
||||
err := node.Decode(&value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prefix, err := netip.ParsePrefix(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*p = ListenPrefix(prefix)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p ListenPrefix) Build() netip.Prefix {
|
||||
return netip.Prefix(p)
|
||||
}
|
||||
|
||||
// IPTables config
|
||||
@ -130,12 +195,14 @@ type IPTables struct {
|
||||
}
|
||||
|
||||
type Sniffer struct {
|
||||
Enable bool
|
||||
Sniffers []sniffer.Type
|
||||
Reverses *trie.DomainTrie[bool]
|
||||
ForceDomain *trie.DomainTrie[bool]
|
||||
SkipDomain *trie.DomainTrie[bool]
|
||||
Ports *[]utils.Range[uint16]
|
||||
Enable bool
|
||||
Sniffers []sniffer.Type
|
||||
Reverses *trie.DomainTrie[bool]
|
||||
ForceDomain *trie.DomainTrie[bool]
|
||||
SkipDomain *trie.DomainTrie[bool]
|
||||
Ports *[]utils.Range[uint16]
|
||||
ForceDnsMapping bool
|
||||
ParsePureIp bool
|
||||
}
|
||||
|
||||
// Experimental config
|
||||
@ -153,6 +220,7 @@ type Config struct {
|
||||
Hosts *trie.DomainTrie[netip.Addr]
|
||||
Profile *Profile
|
||||
Rules []C.Rule
|
||||
SubRules *map[string][]C.Rule
|
||||
Users []auth.AuthUser
|
||||
Proxies map[string]C.Proxy
|
||||
Providers map[string]providerTypes.ProxyProvider
|
||||
@ -193,6 +261,22 @@ type RawTun struct {
|
||||
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
|
||||
AutoDetectInterface bool `yaml:"auto-detect-interface"`
|
||||
RedirectToTun []string `yaml:"-" json:"-"`
|
||||
|
||||
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
||||
//Inet4Address []ListenPrefix `yaml:"inet4-address" json:"inet4_address,omitempty"`
|
||||
Inet6Address []ListenPrefix `yaml:"inet6-address" json:"inet6_address,omitempty"`
|
||||
StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"`
|
||||
Inet4RouteAddress []ListenPrefix `yaml:"inet4_route_address" json:"inet4_route_address,omitempty"`
|
||||
Inet6RouteAddress []ListenPrefix `yaml:"inet6_route_address" json:"inet6_route_address,omitempty"`
|
||||
IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"`
|
||||
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
|
||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
|
||||
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"`
|
||||
IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"`
|
||||
IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"`
|
||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"`
|
||||
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"`
|
||||
UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"`
|
||||
}
|
||||
|
||||
type RawConfig struct {
|
||||
@ -233,6 +317,7 @@ type RawConfig struct {
|
||||
Proxy []map[string]any `yaml:"proxies"`
|
||||
ProxyGroup []map[string]any `yaml:"proxy-groups"`
|
||||
Rule []string `yaml:"rules"`
|
||||
SubRules map[string][]string `yaml:"sub-rules"`
|
||||
}
|
||||
|
||||
type RawGeoXUrl struct {
|
||||
@ -242,11 +327,13 @@ type RawGeoXUrl struct {
|
||||
}
|
||||
|
||||
type RawSniffer struct {
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Sniffing []string `yaml:"sniffing" json:"sniffing"`
|
||||
ForceDomain []string `yaml:"force-domain" json:"force-domain"`
|
||||
SkipDomain []string `yaml:"skip-domain" json:"skip-domain"`
|
||||
Ports []string `yaml:"port-whitelist" json:"port-whitelist"`
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Sniffing []string `yaml:"sniffing" json:"sniffing"`
|
||||
ForceDomain []string `yaml:"force-domain" json:"force-domain"`
|
||||
SkipDomain []string `yaml:"skip-domain" json:"skip-domain"`
|
||||
Ports []string `yaml:"port-whitelist" json:"port-whitelist"`
|
||||
ForceDnsMapping bool `yaml:"force-dns-mapping" json:"force-dns-mapping"`
|
||||
ParsePureIp bool `yaml:"parse-pure-ip" json:"parse-pure-ip"`
|
||||
}
|
||||
|
||||
// EBpf config
|
||||
@ -294,8 +381,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
Device: "",
|
||||
Stack: C.TunGvisor,
|
||||
DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query
|
||||
AutoRoute: false,
|
||||
AutoDetectInterface: false,
|
||||
AutoRoute: true,
|
||||
AutoDetectInterface: true,
|
||||
Inet6Address: []ListenPrefix{ListenPrefix(netip.MustParsePrefix("fdfe:dcba:9876::1/126"))},
|
||||
},
|
||||
EBpf: EBpf{
|
||||
RedirectToTun: []string{},
|
||||
@ -335,11 +423,13 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
},
|
||||
},
|
||||
Sniffer: RawSniffer{
|
||||
Enable: false,
|
||||
Sniffing: []string{},
|
||||
ForceDomain: []string{},
|
||||
SkipDomain: []string{},
|
||||
Ports: []string{},
|
||||
Enable: false,
|
||||
Sniffing: []string{},
|
||||
ForceDomain: []string{},
|
||||
SkipDomain: []string{},
|
||||
Ports: []string{},
|
||||
ForceDnsMapping: true,
|
||||
ParsePureIp: true,
|
||||
},
|
||||
Profile: Profile{
|
||||
StoreSelected: true,
|
||||
@ -381,12 +471,18 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
config.Proxies = proxies
|
||||
config.Providers = providers
|
||||
|
||||
rules, ruleProviders, err := parseRules(rawCfg, proxies)
|
||||
subRules, ruleProviders, err := parseSubRules(rawCfg, proxies)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.SubRules = subRules
|
||||
config.RuleProviders = ruleProviders
|
||||
|
||||
rules, err := parseRules(rawCfg, proxies, subRules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Rules = rules
|
||||
config.RuleProviders = ruleProviders
|
||||
|
||||
hosts, err := parseHosts(rawCfg)
|
||||
if err != nil {
|
||||
@ -563,8 +659,9 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
return proxies, providersMap, nil
|
||||
}
|
||||
|
||||
func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[string]providerTypes.RuleProvider, error) {
|
||||
ruleProviders := map[string]providerTypes.RuleProvider{}
|
||||
func parseSubRules(cfg *RawConfig, proxies map[string]C.Proxy) (subRules *map[string][]C.Rule, ruleProviders map[string]providerTypes.RuleProvider, err error) {
|
||||
ruleProviders = map[string]providerTypes.RuleProvider{}
|
||||
subRules = &map[string][]C.Rule{}
|
||||
log.Infoln("Geodata Loader mode: %s", geodata.LoaderName())
|
||||
// parse rule provider
|
||||
for name, mapping := range cfg.RuleProvider {
|
||||
@ -577,6 +674,102 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
|
||||
RP.SetRuleProvider(rp)
|
||||
}
|
||||
|
||||
for name, rawRules := range cfg.SubRules {
|
||||
var rules []C.Rule
|
||||
for idx, line := range rawRules {
|
||||
rawRule := trimArr(strings.Split(line, ","))
|
||||
var (
|
||||
payload string
|
||||
target string
|
||||
params []string
|
||||
ruleName = strings.ToUpper(rawRule[0])
|
||||
)
|
||||
|
||||
l := len(rawRule)
|
||||
|
||||
if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" || ruleName == "SUB-RULE" {
|
||||
target = rawRule[l-1]
|
||||
payload = strings.Join(rawRule[1:l-1], ",")
|
||||
} else {
|
||||
if l < 2 {
|
||||
return nil, nil, fmt.Errorf("sub-rules[%d] [%s] error: format invalid", idx, line)
|
||||
}
|
||||
if l < 4 {
|
||||
rawRule = append(rawRule, make([]string, 4-l)...)
|
||||
}
|
||||
if ruleName == "MATCH" {
|
||||
l = 2
|
||||
}
|
||||
if l >= 3 {
|
||||
l = 3
|
||||
payload = rawRule[1]
|
||||
}
|
||||
target = rawRule[l-1]
|
||||
params = rawRule[l:]
|
||||
}
|
||||
|
||||
if _, ok := proxies[target]; !ok && ruleName != "SUB-RULE" {
|
||||
return nil, nil, fmt.Errorf("sub-rules[%d:%s] [%s] error: proxy [%s] not found", idx, name, line, target)
|
||||
}
|
||||
|
||||
params = trimArr(params)
|
||||
parsed, parseErr := R.ParseRule(ruleName, payload, target, params, subRules)
|
||||
if parseErr != nil {
|
||||
return nil, nil, fmt.Errorf("sub-rules[%d] [%s] error: %s", idx, line, parseErr.Error())
|
||||
}
|
||||
|
||||
rules = append(rules, parsed)
|
||||
}
|
||||
(*subRules)[name] = rules
|
||||
}
|
||||
|
||||
if err = verifySubRule(subRules); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func verifySubRule(subRules *map[string][]C.Rule) error {
|
||||
for name := range *subRules {
|
||||
err := verifySubRuleCircularReferences(name, subRules, []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifySubRuleCircularReferences(n string, subRules *map[string][]C.Rule, arr []string) error {
|
||||
isInArray := func(v string, array []string) bool {
|
||||
for _, c := range array {
|
||||
if v == c {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
arr = append(arr, n)
|
||||
for i, rule := range (*subRules)[n] {
|
||||
if rule.RuleType() == C.SubRules {
|
||||
if _, ok := (*subRules)[rule.Adapter()]; !ok {
|
||||
return fmt.Errorf("sub-rule[%d:%s] error: [%s] not found", i, n, rule.Adapter())
|
||||
}
|
||||
if isInArray(rule.Adapter(), arr) {
|
||||
arr = append(arr, rule.Adapter())
|
||||
return fmt.Errorf("sub-rule error: circular references [%s]", strings.Join(arr, "->"))
|
||||
}
|
||||
|
||||
if err := verifySubRuleCircularReferences(rule.Adapter(), subRules, arr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseRules(cfg *RawConfig, proxies map[string]C.Proxy, subRules *map[string][]C.Rule) ([]C.Rule, error) {
|
||||
var rules []C.Rule
|
||||
rulesConfig := cfg.Rule
|
||||
|
||||
@ -592,12 +785,12 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
|
||||
|
||||
l := len(rule)
|
||||
|
||||
if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" {
|
||||
if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" || ruleName == "SUB-RULE" {
|
||||
target = rule[l-1]
|
||||
payload = strings.Join(rule[1:l-1], ",")
|
||||
} else {
|
||||
if l < 2 {
|
||||
return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
|
||||
return nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
|
||||
}
|
||||
if l < 4 {
|
||||
rule = append(rule, make([]string, 4-l)...)
|
||||
@ -612,15 +805,18 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
|
||||
target = rule[l-1]
|
||||
params = rule[l:]
|
||||
}
|
||||
|
||||
if _, ok := proxies[target]; !ok {
|
||||
return nil, nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
|
||||
if ruleName != "SUB-RULE" {
|
||||
return nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
|
||||
} else if _, ok = (*subRules)[target]; !ok {
|
||||
return nil, fmt.Errorf("rules[%d] [%s] error: sub-rule [%s] not found", idx, line, target)
|
||||
}
|
||||
}
|
||||
|
||||
params = trimArr(params)
|
||||
parsed, parseErr := R.ParseRule(ruleName, payload, target, params)
|
||||
parsed, parseErr := R.ParseRule(ruleName, payload, target, params, subRules)
|
||||
if parseErr != nil {
|
||||
return nil, nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
|
||||
return nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
|
||||
}
|
||||
|
||||
rules = append(rules, parsed)
|
||||
@ -628,7 +824,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
|
||||
|
||||
runtime.GC()
|
||||
|
||||
return rules, ruleProviders, nil
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func parseHosts(cfg *RawConfig) (*trie.DomainTrie[netip.Addr], error) {
|
||||
@ -931,17 +1127,6 @@ func parseAuthentication(rawRecords []string) []auth.AuthUser {
|
||||
}
|
||||
|
||||
func parseTun(rawTun RawTun, general *General, dnsCfg *DNS) (*Tun, error) {
|
||||
if rawTun.Enable && rawTun.AutoDetectInterface {
|
||||
autoDetectInterfaceName, err := commons.GetAutoDetectInterface()
|
||||
if err != nil {
|
||||
log.Warnln("Can not find auto detect interface.[%s]", err)
|
||||
} else {
|
||||
log.Warnln("Auto detect interface: %s", autoDetectInterfaceName)
|
||||
}
|
||||
|
||||
general.Interface = autoDetectInterfaceName
|
||||
}
|
||||
|
||||
var dnsHijack []netip.AddrPort
|
||||
|
||||
for _, d := range rawTun.DNSHijack {
|
||||
@ -963,6 +1148,7 @@ func parseTun(rawTun RawTun, general *General, dnsCfg *DNS) (*Tun, error) {
|
||||
} else {
|
||||
tunAddressPrefix = netip.MustParsePrefix("198.18.0.1/16")
|
||||
}
|
||||
tunAddressPrefix = netip.PrefixFrom(tunAddressPrefix.Addr(), 30)
|
||||
|
||||
return &Tun{
|
||||
Enable: rawTun.Enable,
|
||||
@ -971,14 +1157,31 @@ func parseTun(rawTun RawTun, general *General, dnsCfg *DNS) (*Tun, error) {
|
||||
DNSHijack: dnsHijack,
|
||||
AutoRoute: rawTun.AutoRoute,
|
||||
AutoDetectInterface: rawTun.AutoDetectInterface,
|
||||
TunAddressPrefix: tunAddressPrefix,
|
||||
RedirectToTun: rawTun.RedirectToTun,
|
||||
|
||||
MTU: rawTun.MTU,
|
||||
Inet4Address: []ListenPrefix{ListenPrefix(tunAddressPrefix)},
|
||||
Inet6Address: rawTun.Inet6Address,
|
||||
StrictRoute: rawTun.StrictRoute,
|
||||
Inet4RouteAddress: rawTun.Inet4RouteAddress,
|
||||
Inet6RouteAddress: rawTun.Inet6RouteAddress,
|
||||
IncludeUID: rawTun.IncludeUID,
|
||||
IncludeUIDRange: rawTun.IncludeUIDRange,
|
||||
ExcludeUID: rawTun.ExcludeUID,
|
||||
ExcludeUIDRange: rawTun.ExcludeUIDRange,
|
||||
IncludeAndroidUser: rawTun.IncludeAndroidUser,
|
||||
IncludePackage: rawTun.IncludePackage,
|
||||
ExcludePackage: rawTun.ExcludePackage,
|
||||
EndpointIndependentNat: rawTun.EndpointIndependentNat,
|
||||
UDPTimeout: rawTun.UDPTimeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
|
||||
sniffer := &Sniffer{
|
||||
Enable: snifferRaw.Enable,
|
||||
Enable: snifferRaw.Enable,
|
||||
ForceDnsMapping: snifferRaw.ForceDnsMapping,
|
||||
ParsePureIp: snifferRaw.ParsePureIp,
|
||||
}
|
||||
|
||||
var ports []utils.Range[uint16]
|
||||
|
@ -106,12 +106,13 @@ type ProxyAdapter interface {
|
||||
ListenPacketOnStreamConn(c net.Conn, metadata *Metadata) (PacketConn, error)
|
||||
|
||||
// Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract.
|
||||
Unwrap(metadata *Metadata) Proxy
|
||||
Unwrap(metadata *Metadata, touch bool) Proxy
|
||||
}
|
||||
|
||||
type Group interface {
|
||||
URLTest(ctx context.Context, url string) (mp map[string]uint16, err error)
|
||||
GetProxies(touch bool) []Proxy
|
||||
Touch()
|
||||
}
|
||||
|
||||
type DelayHistory struct {
|
||||
|
@ -16,6 +16,7 @@ const (
|
||||
DNSNormal DNSMode = iota
|
||||
DNSFakeIP
|
||||
DNSMapping
|
||||
DNSHosts
|
||||
)
|
||||
|
||||
type DNSMode int
|
||||
@ -64,6 +65,8 @@ func (e DNSMode) String() string {
|
||||
return "fake-ip"
|
||||
case DNSMapping:
|
||||
return "redir-host"
|
||||
case DNSHosts:
|
||||
return "hosts"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ func (m *Metadata) Resolved() bool {
|
||||
// Pure is used to solve unexpected behavior
|
||||
// when dialing proxy connection in DNSMapping mode.
|
||||
func (m *Metadata) Pure() *Metadata {
|
||||
if m.DNSMode == DNSMapping && m.DstIP.IsValid() {
|
||||
if (m.DNSMode == DNSMapping || m.DNSMode == DNSHosts) && m.DstIP.IsValid() {
|
||||
copyM := *m
|
||||
copyM.Host = ""
|
||||
if copyM.DstIP.Is4() {
|
||||
|
@ -19,6 +19,7 @@ const (
|
||||
Network
|
||||
Uid
|
||||
INTYPE
|
||||
SubRules
|
||||
MATCH
|
||||
AND
|
||||
OR
|
||||
@ -65,6 +66,8 @@ func (rt RuleType) String() string {
|
||||
return "Uid"
|
||||
case INTYPE:
|
||||
return "InType"
|
||||
case SubRules:
|
||||
return "SubRules"
|
||||
case AND:
|
||||
return "AND"
|
||||
case OR:
|
||||
@ -78,7 +81,7 @@ func (rt RuleType) String() string {
|
||||
|
||||
type Rule interface {
|
||||
RuleType() RuleType
|
||||
Match(metadata *Metadata) bool
|
||||
Match(metadata *Metadata) (bool, string)
|
||||
Adapter() string
|
||||
Payload() string
|
||||
ShouldResolveIP() bool
|
||||
|
@ -7,13 +7,15 @@ import (
|
||||
)
|
||||
|
||||
var StackTypeMapping = map[string]TUNStack{
|
||||
strings.ToUpper(TunGvisor.String()): TunGvisor,
|
||||
strings.ToUpper(TunSystem.String()): TunSystem,
|
||||
strings.ToLower(TunGvisor.String()): TunGvisor,
|
||||
strings.ToLower(TunSystem.String()): TunSystem,
|
||||
strings.ToLower(TunLWIP.String()): TunLWIP,
|
||||
}
|
||||
|
||||
const (
|
||||
TunGvisor TUNStack = iota
|
||||
TunSystem
|
||||
TunLWIP
|
||||
)
|
||||
|
||||
type TUNStack int
|
||||
@ -24,7 +26,7 @@ func (e *TUNStack) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
if err := unmarshal(&tp); err != nil {
|
||||
return err
|
||||
}
|
||||
mode, exist := StackTypeMapping[strings.ToUpper(tp)]
|
||||
mode, exist := StackTypeMapping[strings.ToLower(tp)]
|
||||
if !exist {
|
||||
return errors.New("invalid tun stack")
|
||||
}
|
||||
@ -41,7 +43,7 @@ func (e TUNStack) MarshalYAML() (any, error) {
|
||||
func (e *TUNStack) UnmarshalJSON(data []byte) error {
|
||||
var tp string
|
||||
json.Unmarshal(data, &tp)
|
||||
mode, exist := StackTypeMapping[strings.ToUpper(tp)]
|
||||
mode, exist := StackTypeMapping[strings.ToLower(tp)]
|
||||
if !exist {
|
||||
return errors.New("invalid tun stack")
|
||||
}
|
||||
@ -60,6 +62,8 @@ func (e TUNStack) String() string {
|
||||
return "gVisor"
|
||||
case TunSystem:
|
||||
return "System"
|
||||
case TunLWIP:
|
||||
return "LWIP"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
CN "github.com/Dreamacro/clash/common/net"
|
||||
"net"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
@ -20,7 +21,7 @@ func NewConnContext(conn net.Conn, metadata *C.Metadata) *ConnContext {
|
||||
return &ConnContext{
|
||||
id: id,
|
||||
metadata: metadata,
|
||||
conn: CN.NewBufferedConn(conn),
|
||||
conn: N.NewBufferedConn(conn),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,7 +134,7 @@ dns:
|
||||
# 配置强制 fallback,优先于 IP 判断,具体分类自行查看 geosite 库
|
||||
# geosite:
|
||||
# - gfw
|
||||
# 配置不需要使用 fallback 的 IP CIDR
|
||||
# 如果不匹配 ipcidr 则使用 nameservers 中的结果
|
||||
# ipcidr:
|
||||
# - 240.0.0.0/4
|
||||
# domain:
|
||||
@ -162,14 +162,14 @@ proxies:
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: "password"
|
||||
# udp: true
|
||||
# udp-over-tcp: false
|
||||
# ip-version: ipv4 # 设置节点使用 IP 版本,可选:dual,ipv4,ipv6,ipv4-prefer,ipv6-prefer。默认使用 dual
|
||||
# ipv4:仅使用 IPv4 ipv6:仅使用 IPv6
|
||||
# ipv4-prefer:优先使用 IPv4 对于 TCP 会进行双栈解析,并发链接但是优先使用 IPv4 链接,
|
||||
# UDP 则为双栈解析,获取结果中的第一个 IPv4
|
||||
# ipv6-prefer 同 ipv4-prefer
|
||||
# 现有协议都支持此参数,TCP 效果仅在开启 tcp-concurrent 生效
|
||||
# udp: true
|
||||
# udp-over-tcp: false
|
||||
# ip-version: ipv4 # 设置节点使用 IP 版本,可选:dual,ipv4,ipv6,ipv4-prefer,ipv6-prefer。默认使用 dual
|
||||
# ipv4:仅使用 IPv4 ipv6:仅使用 IPv6
|
||||
# ipv4-prefer:优先使用 IPv4 对于 TCP 会进行双栈解析,并发链接但是优先使用 IPv4 链接,
|
||||
# UDP 则为双栈解析,获取结果中的第一个 IPv4
|
||||
# ipv6-prefer 同 ipv4-prefer
|
||||
# 现有协议都支持此参数,TCP 效果仅在开启 tcp-concurrent 生效
|
||||
- name: "ss2"
|
||||
type: ss
|
||||
server: server
|
||||
@ -391,13 +391,16 @@ proxies:
|
||||
path: "/"
|
||||
headers:
|
||||
Host: example.com
|
||||
|
||||
#hysteria
|
||||
- name: "hysteria"
|
||||
type: hysteria
|
||||
server: server.com
|
||||
port: 443
|
||||
auth_str: yourpassword
|
||||
# obfs: obfs_str
|
||||
# alpn: h3
|
||||
# alpn:
|
||||
# - h3
|
||||
protocol: udp # 支持 udp/wechat-video/faketcp
|
||||
up: "30 Mbps" # 若不写单位,默认为 Mbps
|
||||
down: "200 Mbps" # 若不写单位,默认为 Mbps
|
||||
@ -538,3 +541,29 @@ rules:
|
||||
- DOMAIN-KEYWORD,google,ss1
|
||||
- IP-CIDR,1.1.1.1/32,ss1
|
||||
- IP-CIDR6,2409::/64,DIRECT
|
||||
- SUB-RULE,(OR,((NETWORK,TCP),(NETWORK,UDP))),sub-rule-name1 # 当满足条件是 TCP 或 UDP 流量时,使用名为 sub-rule-name1 当规则集
|
||||
- SUB-RULE,(AND,((NETWORK,UDP))),sub-rule-name2
|
||||
# 定义多个子规则集,规则将以分叉匹配,使用 SUB-RULE 使用
|
||||
# google.com(not match)--> baidu.com(match)
|
||||
# / |
|
||||
# / |
|
||||
# https://baidu.com --> rule1 --> rule2 --> sub-rule-name1(match tcp) 使用 DIRECT
|
||||
#
|
||||
#
|
||||
# google.com(not match)--> baidu.com(not match)
|
||||
# / |
|
||||
# / |
|
||||
# dns 1.1.1.1 --> rule1 --> rule2 --> sub-rule-name1(match udp) sub-rule-name2(match udp)
|
||||
# |
|
||||
# |
|
||||
# 使用 REJECT <-- 1.1.1.1/32(match)
|
||||
#
|
||||
|
||||
sub-rules:
|
||||
sub-rule-name1:
|
||||
- DOMAIN,google.com,ss1
|
||||
- DOMAIN,baidu.com,DIRECT
|
||||
sub-rule-name2:
|
||||
- IP-CIDR,1.1.1.1/32,REJECT
|
||||
- IP-CIDR,8.8.8.8/32,ss1
|
||||
- DOMAIN,dns.alidns.com,REJECT
|
||||
|
43
flake.lock
generated
Normal file
43
flake.lock
generated
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1664705630,
|
||||
"narHash": "sha256-MLi1J9tIZQFj8v9RKmG89HJAE5ja3z4ui4Tf9+wG/bM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "f71b215225dec75df6266ff7764d54c2e44ef226",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "master",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"utils": "utils"
|
||||
}
|
||||
},
|
||||
"utils": {
|
||||
"locked": {
|
||||
"lastModified": 1659877975,
|
||||
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
61
flake.nix
Normal file
61
flake.nix
Normal file
@ -0,0 +1,61 @@
|
||||
{
|
||||
description = "Another Clash Kernel";
|
||||
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/master";
|
||||
|
||||
inputs.utils.url = "github:numtide/flake-utils";
|
||||
|
||||
outputs = { self, nixpkgs, utils }:
|
||||
utils.lib.eachDefaultSystem
|
||||
(system:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [ self.overlay ];
|
||||
};
|
||||
in
|
||||
rec {
|
||||
packages.default = pkgs.clash-meta;
|
||||
}
|
||||
) //
|
||||
(
|
||||
let version = nixpkgs.lib.substring 0 8 self.lastModifiedDate or self.lastModified or "19700101"; in
|
||||
{
|
||||
overlay = final: prev: {
|
||||
|
||||
clash-meta = final.buildGoModule {
|
||||
pname = "clash-meta";
|
||||
inherit version;
|
||||
src = ./.;
|
||||
|
||||
vendorSha256 = "sha256-yhq4WHQcS4CrdcO6KJ5tSn4m7l5g1lNgE9/2BWd9Iys=";
|
||||
|
||||
# Do not build testing suit
|
||||
excludedPackages = [ "./test" ];
|
||||
|
||||
CGO_ENABLED = 0;
|
||||
|
||||
ldflags = [
|
||||
"-s"
|
||||
"-w"
|
||||
"-X github.com/Dreamacro/clash/constant.Version=dev-${version}"
|
||||
"-X github.com/Dreamacro/clash/constant.BuildTime=${version}"
|
||||
];
|
||||
|
||||
tags = [
|
||||
"with_gvisor"
|
||||
];
|
||||
|
||||
# Network required
|
||||
doCheck = false;
|
||||
|
||||
postInstall = ''
|
||||
mv $out/bin/clash $out/bin/clash-meta
|
||||
'';
|
||||
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
72
go.mod
72
go.mod
@ -3,43 +3,41 @@ module github.com/Dreamacro/clash
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/cilium/ebpf v0.9.1
|
||||
github.com/cilium/ebpf v0.9.3
|
||||
github.com/coreos/go-iptables v0.6.0
|
||||
github.com/database64128/tfo-go v1.1.0
|
||||
github.com/dlclark/regexp2 v1.4.0
|
||||
github.com/database64128/tfo-go v1.1.2
|
||||
github.com/dlclark/regexp2 v1.7.0
|
||||
github.com/go-chi/chi/v5 v5.0.7
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.1
|
||||
github.com/gofrs/uuid v4.2.0+incompatible
|
||||
github.com/go-chi/render v1.0.2
|
||||
github.com/gofrs/uuid v4.3.0+incompatible
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f
|
||||
github.com/lucas-clemente/quic-go v0.27.2
|
||||
github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c
|
||||
github.com/lucas-clemente/quic-go v0.29.1
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||
github.com/miekg/dns v1.1.49
|
||||
github.com/oschwald/geoip2-golang v1.7.0
|
||||
github.com/sagernet/sing v0.0.0-20220627234642-a817f7084d9c
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220627234717-689e0165ef2c
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220616051646-3d3fc5d01eec
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.7.2
|
||||
github.com/miekg/dns v1.1.50
|
||||
github.com/oschwald/geoip2-golang v1.8.0
|
||||
github.com/sagernet/sing v0.0.0-20220929000216-9a83e35b7186
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
|
||||
github.com/sagernet/sing-tun v0.0.0-20221012082254-488c3b75f6fd
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220921140858-b6a1bdee672f
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2
|
||||
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672
|
||||
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
go.uber.org/atomic v1.9.0
|
||||
go.uber.org/atomic v1.10.0
|
||||
go.uber.org/automaxprocs v1.5.1
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
|
||||
golang.org/x/exp v0.0.0-20220608143224-64259d1afd70
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
|
||||
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4
|
||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e
|
||||
google.golang.org/protobuf v1.28.0
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9
|
||||
golang.org/x/net v0.0.0-20221004154528-8021a29435af
|
||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0
|
||||
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875
|
||||
google.golang.org/protobuf v1.28.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gvisor.dev/gvisor v0.0.0-20220810234332-45096a971e66
|
||||
|
||||
)
|
||||
|
||||
replace github.com/vishvananda/netlink => github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820
|
||||
@ -47,29 +45,33 @@ replace github.com/vishvananda/netlink => github.com/MetaCubeX/netlink v1.2.0-be
|
||||
replace github.com/lucas-clemente/quic-go => github.com/tobyxdd/quic-go v0.28.1-0.20220706211558-7780039ad599
|
||||
|
||||
require (
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/cheekybits/genny v1.0.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.1.1 // indirect
|
||||
github.com/marten-seemann/qpack v0.2.1 // indirect
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 // indirect
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.9.0 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e // indirect
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
|
||||
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20220913150850-18c4f4234207 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab // indirect
|
||||
golang.org/x/tools v0.1.10 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c // indirect
|
||||
lukechampine.com/blake3 v1.1.7 // indirect
|
||||
)
|
||||
|
147
go.sum
147
go.sum
@ -10,25 +10,27 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820 h1:fGKWZ25VApYnuPZoNeqdH/nZtHa2XMajwH6Yj/OgoVc=
|
||||
github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/cilium/ebpf v0.9.1 h1:64sn2K3UKw8NbP/blsixRpF3nXuyhz/VjRlRzvlBRu4=
|
||||
github.com/cilium/ebpf v0.9.1/go.mod h1:+OhNOIXx/Fnu1IE8bJz2dzOA+VSfyTfdNUVdlQnxUFY=
|
||||
github.com/cilium/ebpf v0.9.3 h1:5KtxXZU+scyERvkJMEm16TbScVvuuMrlhPly78ZMbSc=
|
||||
github.com/cilium/ebpf v0.9.3/go.mod h1:w27N4UjpaQ9X/DGrSugxUG+H+NhgntDuPb5lCzxCn8A=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
|
||||
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/database64128/tfo-go v1.1.0 h1:VO0polyGNSAmr99nYw9GQeMz7ZOcQ/QbjlTwniHwfTQ=
|
||||
github.com/database64128/tfo-go v1.1.0/go.mod h1:95pOT8bnV3P2Lmu9upHNWFHz6dYGJ9cr7pnb0tGQAG8=
|
||||
github.com/database64128/tfo-go v1.1.2 h1:GwxtJp09BdUTVEoeT421t231eNZoGOCRkklbl4WI1kU=
|
||||
github.com/database64128/tfo-go v1.1.2/go.mod h1:jgrSUPyOvTGQyn6irCOpk7L2W/q/0VLZZcovQiMi+bI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
@ -44,13 +46,13 @@ github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
|
||||
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
|
||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||
github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
|
||||
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
|
||||
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v4.3.0+incompatible h1:CaSVZxm5B+7o45rtab4jC2G37WGYX1zQfuU2i6DSvnc=
|
||||
github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
@ -71,15 +73,15 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
@ -97,8 +99,8 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f h1:l1QCwn715k8nYkj4Ql50rzEog3WnMdrd4YYMMwemxEo=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c h1:OCFM4+DXTWfNlyeoddrTwdup/ztkGSyAMR2UGcPckNQ=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
|
||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||
@ -109,8 +111,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0=
|
||||
github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@ -129,8 +131,9 @@ github.com/marten-seemann/qtls-go1-17 v0.1.2 h1:JADBlm0LYiVbuSySCHeY863dNkcpMmDR
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.2/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 h1:7m/WlWcSROrcK5NxuXaxYD32BZqe/LEEnBrWcH/cOqQ=
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0 h1:rLFKD/9mp/uq1SYGYuVZhm83wkmU95pK5df3GufyYYU=
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
|
||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||
@ -140,8 +143,8 @@ github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZ
|
||||
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/miekg/dns v1.1.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8=
|
||||
github.com/miekg/dns v1.1.49/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
@ -161,10 +164,10 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
|
||||
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
|
||||
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/oschwald/geoip2-golang v1.7.0 h1:JW1r5AKi+vv2ujSxjKthySK3jo8w8oKWPyXsw+Qs/S8=
|
||||
github.com/oschwald/geoip2-golang v1.7.0/go.mod h1:mdI/C7iK7NVMcIDDtf4bCKMJ7r0o7UwGeCo9eiitCMQ=
|
||||
github.com/oschwald/maxminddb-golang v1.9.0 h1:tIk4nv6VT9OiPyrnDAfJS1s1xKDQMZOsGojab6EjC1Y=
|
||||
github.com/oschwald/maxminddb-golang v1.9.0/go.mod h1:TK+s/Z2oZq0rSl4PSeAEoP0bgm82Cp5HyvYbt8K3zLY=
|
||||
github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6SLNcKTs=
|
||||
github.com/oschwald/geoip2-golang v1.8.0/go.mod h1:R7bRvYjOeaoenAp9sKRS8GX5bJWcZ0laWO5+DauEktw=
|
||||
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
|
||||
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@ -175,12 +178,21 @@ github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7q
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sagernet/sing v0.0.0-20220627234642-a817f7084d9c h1:98QC0wtaD648MFPw82KaT1O9LloQgR4ZyIDtNtsno8Y=
|
||||
github.com/sagernet/sing v0.0.0-20220627234642-a817f7084d9c/go.mod h1:I67R/q5f67xDExL2kL3RLIP7kGJBOPkYXkpRAykgC+E=
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220627234717-689e0165ef2c h1:Jhgjyb2jXL4GtwJec6/kgeTqaQXsvMiNX2wAkGOSD3I=
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220627234717-689e0165ef2c/go.mod h1:ng5pxdNnKZWlxzZTXRqWeY0ftzhScPZmjgJGJeRuPYY=
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220616051646-3d3fc5d01eec h1:jUSfKmyL6K9O2TvIvcVacZ4eNXHYbNSfdph+DRPyVlU=
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220616051646-3d3fc5d01eec/go.mod h1:jDZ8fJgOea7Y7MMHWgfqwLBVLnhtW2zuxS5wjtDaB84=
|
||||
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e h1:5CFRo8FJbCuf5s/eTBdZpmMbn8Fe2eSMLNAYfKanA34=
|
||||
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e/go.mod h1:qbt0dWObotCfcjAJJ9AxtFPNSDUfZF+6dCpgKEOBn/g=
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||
github.com/sagernet/sing v0.0.0-20220929000216-9a83e35b7186 h1:ZDlgH6dTozS3ODaYq1GxCj+H8NvYESaex90iX72gadw=
|
||||
github.com/sagernet/sing v0.0.0-20220929000216-9a83e35b7186/go.mod h1:zvgDYKI+vCAW9RyfyrKTgleI+DOa8lzHMPC7VZo3OL4=
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM=
|
||||
github.com/sagernet/sing-tun v0.0.0-20221012082254-488c3b75f6fd h1:TtoZDwg09Cpqi+gCmCtL6w4oEUZ5lHz+vHIjdr1UBNY=
|
||||
github.com/sagernet/sing-tun v0.0.0-20221012082254-488c3b75f6fd/go.mod h1:1u3pjXA9HmH7kRiBJqM3C/zPxrxnCLd3svmqtub/RFU=
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220921140858-b6a1bdee672f h1:xyJ3Wbibcug4DxLi/FCHX2Td667SfieyZv645b8+eEE=
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220921140858-b6a1bdee672f/go.mod h1:bwhAdSNET1X+j9DOXGj9NIQR39xgcWIk1rOQ9lLD+gM=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||
@ -204,19 +216,21 @@ github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1l
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tobyxdd/quic-go v0.28.1-0.20220706211558-7780039ad599 h1:We+z04jRpTGxFggeGWf+GbinhlIk1I1kMMEgujhUfiA=
|
||||
github.com/tobyxdd/quic-go v0.28.1-0.20220706211558-7780039ad599/go.mod h1:oGz5DKK41cJt5+773+BSO9BXDsREY4HLf7+0odGAPO0=
|
||||
@ -226,17 +240,17 @@ github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4/go.mod h1:LpEX5FO/cB+WF
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 h1:4mkzGhKqt3JO1BWYjtD3iRFyAx4ow67hmSqOcGjuxqQ=
|
||||
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672/go.mod h1:YGGVbz9cOxyKFUmhW7LGaLZaMA0cPlHJinvAmVxEMSU=
|
||||
github.com/vishvananda/netns v0.0.0-20220913150850-18c4f4234207 h1:nn7SOQy8xCu3iXNv7oiBhhEQtbWdnEOMnuKBlHvrqIM=
|
||||
github.com/vishvananda/netns v0.0.0-20220913150850-18c4f4234207/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837 h1:AHhUwwFJGl27E46OpdJHplZkK09m7aETNBNzhT6t15M=
|
||||
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837/go.mod h1:YJTRELIWrGxR1s8xcEBgxcxBfwQfMGjdvNLTjN9XFgY=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk=
|
||||
go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
@ -247,11 +261,11 @@ golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A=
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20220608143224-64259d1afd70 h1:8uGxpY2cLF9H/NSHUiEWUIBZqIcsMzMWIMPCCUkyYgc=
|
||||
golang.org/x/exp v0.0.0-20220608143224-64259d1afd70/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4=
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
@ -259,8 +273,8 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPI
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -285,8 +299,9 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4=
|
||||
golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@ -299,8 +314,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpiB4nXvw1XYzFPGgzLUOY=
|
||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -315,7 +330,6 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -337,8 +351,11 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/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/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM=
|
||||
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -351,8 +368,8 @@ golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab h1:eHo2TTVBaAPw9lDGK2Gb9G
|
||||
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@ -365,20 +382,12 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618=
|
||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4 h1:QlbNZ9SwDAepRQwgeWHLi3rfEMo/kVEU4SmgsNM7HmQ=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e h1:yV04h6Tx19uDR6LvuEbR19cDU+3QrB9LuGjtF7F5G0w=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e/go.mod h1:1CeiatTZwcwSFA3cAtMm8CQoroviTldnxd7DOgM/vI4=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
@ -403,8 +412,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
@ -423,8 +432,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
gvisor.dev/gvisor v0.0.0-20220810234332-45096a971e66 h1:GrHxpIMY0lHZ3Q8rp3m4iOb0pJsnCQ/5AHaN9SXE69E=
|
||||
gvisor.dev/gvisor v0.0.0-20220810234332-45096a971e66/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
|
||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
@ -265,7 +265,10 @@ func updateTun(tun *config.Tun) {
|
||||
|
||||
func updateSniffer(sniffer *config.Sniffer) {
|
||||
if sniffer.Enable {
|
||||
dispatcher, err := SNI.NewSnifferDispatcher(sniffer.Sniffers, sniffer.ForceDomain, sniffer.SkipDomain, sniffer.Ports)
|
||||
dispatcher, err := SNI.NewSnifferDispatcher(
|
||||
sniffer.Sniffers, sniffer.ForceDomain, sniffer.SkipDomain, sniffer.Ports,
|
||||
sniffer.ForceDnsMapping, sniffer.ParsePureIp,
|
||||
)
|
||||
if err != nil {
|
||||
log.Warnln("initial sniffer failed, err:%v", err)
|
||||
}
|
||||
|
@ -2,10 +2,7 @@ package proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/component/ebpf"
|
||||
"github.com/Dreamacro/clash/listener/autoredir"
|
||||
"github.com/Dreamacro/clash/listener/inner"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
||||
"github.com/Dreamacro/clash/listener/sing_tun"
|
||||
"golang.org/x/exp/slices"
|
||||
"net"
|
||||
"sort"
|
||||
@ -13,15 +10,16 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/component/ebpf"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/listener/autoredir"
|
||||
"github.com/Dreamacro/clash/listener/http"
|
||||
"github.com/Dreamacro/clash/listener/inner"
|
||||
"github.com/Dreamacro/clash/listener/mixed"
|
||||
"github.com/Dreamacro/clash/listener/redir"
|
||||
"github.com/Dreamacro/clash/listener/socks"
|
||||
"github.com/Dreamacro/clash/listener/tproxy"
|
||||
"github.com/Dreamacro/clash/listener/tun"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
@ -40,7 +38,7 @@ var (
|
||||
tproxyUDPListener *tproxy.UDPListener
|
||||
mixedListener *mixed.Listener
|
||||
mixedUDPLister *socks.UDPListener
|
||||
tunStackListener ipstack.Stack
|
||||
tunLister *sing_tun.Listener
|
||||
autoRedirListener *autoredir.Listener
|
||||
autoRedirProgram *ebpf.TcEBpfProgram
|
||||
tcProgram *ebpf.TcEBpfProgram
|
||||
@ -350,6 +348,9 @@ func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *
|
||||
}()
|
||||
|
||||
if !hasTunConfigChange(tunConf) {
|
||||
if tunLister != nil {
|
||||
tunLister.FlushDefaultInterface()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -359,7 +360,7 @@ func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *
|
||||
return
|
||||
}
|
||||
|
||||
tunStackListener, err = tun.New(tunConf, tcpIn, udpIn)
|
||||
tunLister, err = sing_tun.New(*tunConf, tcpIn, udpIn)
|
||||
|
||||
lastTunConf = tunConf
|
||||
}
|
||||
@ -429,7 +430,7 @@ func ReCreateAutoRedir(ifaceNames []string, tcpIn chan<- C.ConnContext, _ chan<-
|
||||
return
|
||||
}
|
||||
|
||||
defaultRouteInterfaceName, err := commons.GetAutoDetectInterface()
|
||||
defaultRouteInterfaceName, err := ebpf.GetAutoDetectInterface()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -538,7 +539,7 @@ func hasTunConfigChange(tunConf *config.Tun) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
if tunConf.TunAddressPrefix.String() != lastTunConf.TunAddressPrefix.String() {
|
||||
if slices.Equal(tunConf.Inet4Address, lastTunConf.Inet4Address) && slices.Equal(tunConf.Inet6Address, lastTunConf.Inet6Address) {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -546,16 +547,9 @@ func hasTunConfigChange(tunConf *config.Tun) bool {
|
||||
}
|
||||
|
||||
func Cleanup(wait bool) {
|
||||
if tunStackListener != nil {
|
||||
_ = tunStackListener.Close()
|
||||
commons.StopDefaultInterfaceChangeMonitor()
|
||||
|
||||
if wait {
|
||||
commons.WaitForTunClose(lastTunConf.Device)
|
||||
}
|
||||
|
||||
commons.CleanupRule()
|
||||
if tunLister != nil {
|
||||
tunLister.Close()
|
||||
tunLister = nil
|
||||
}
|
||||
tunStackListener = nil
|
||||
lastTunConf = nil
|
||||
}
|
||||
|
41
listener/sing/log.go
Normal file
41
listener/sing/log.go
Normal file
@ -0,0 +1,41 @@
|
||||
package sing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
L "github.com/sagernet/sing/common/logger"
|
||||
)
|
||||
|
||||
type logger struct{}
|
||||
|
||||
func (l logger) Trace(args ...any) {
|
||||
log.Debugln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l logger) Debug(args ...any) {
|
||||
log.Debugln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l logger) Info(args ...any) {
|
||||
log.Infoln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l logger) Warn(args ...any) {
|
||||
log.Warnln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l logger) Error(args ...any) {
|
||||
log.Errorln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l logger) Fatal(args ...any) {
|
||||
log.Fatalln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func (l logger) Panic(args ...any) {
|
||||
log.Fatalln(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
var Logger L.Logger = logger{}
|
151
listener/sing/sing.go
Normal file
151
listener/sing/sing.go
Normal file
@ -0,0 +1,151 @@
|
||||
package sing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
|
||||
vmess "github.com/sagernet/sing-vmess"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/uot"
|
||||
)
|
||||
|
||||
const UDPTimeout = 5 * time.Minute
|
||||
|
||||
type ListenerHandler struct {
|
||||
TcpIn chan<- C.ConnContext
|
||||
UdpIn chan<- *inbound.PacketAdapter
|
||||
Type C.Type
|
||||
}
|
||||
|
||||
type waitCloseConn struct {
|
||||
net.Conn
|
||||
wg *sync.WaitGroup
|
||||
close sync.Once
|
||||
rAddr net.Addr
|
||||
}
|
||||
|
||||
func (c *waitCloseConn) Close() error { // call from handleTCPConn(connCtx C.ConnContext)
|
||||
c.close.Do(func() {
|
||||
c.wg.Done()
|
||||
})
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
func (c *waitCloseConn) RemoteAddr() net.Addr {
|
||||
return c.rAddr
|
||||
}
|
||||
|
||||
func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
switch metadata.Destination.Fqdn {
|
||||
case vmess.MuxDestination.Fqdn:
|
||||
return vmess.HandleMuxConnection(ctx, conn, h)
|
||||
case uot.UOTMagicAddress:
|
||||
metadata.Destination = M.Socksaddr{}
|
||||
return h.NewPacketConnection(ctx, uot.NewClientConn(conn), metadata)
|
||||
}
|
||||
target := socks5.ParseAddr(metadata.Destination.String())
|
||||
wg := &sync.WaitGroup{}
|
||||
defer wg.Wait() // this goroutine must exit after conn.Close()
|
||||
wg.Add(1)
|
||||
h.TcpIn <- inbound.NewSocket(target, &waitCloseConn{Conn: conn, wg: wg, rAddr: metadata.Source.TCPAddr()}, h.Type)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata M.Metadata) error {
|
||||
defer func() { _ = conn.Close() }()
|
||||
mutex := sync.Mutex{}
|
||||
conn2 := conn // a new interface to set nil in defer
|
||||
defer func() {
|
||||
mutex.Lock() // this goroutine must exit after all conn.WritePacket() is not running
|
||||
defer mutex.Unlock()
|
||||
conn2 = nil
|
||||
}()
|
||||
for {
|
||||
buff := buf.NewPacket() // do not use stack buffer
|
||||
dest, err := conn.ReadPacket(buff)
|
||||
if err != nil {
|
||||
buff.Release()
|
||||
if E.IsClosed(err) {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
target := socks5.ParseAddr(dest.String())
|
||||
packet := &packet{
|
||||
conn: &conn2,
|
||||
mutex: &mutex,
|
||||
rAddr: metadata.Source.UDPAddr(),
|
||||
lAddr: conn.LocalAddr(),
|
||||
buff: buff,
|
||||
}
|
||||
select {
|
||||
case h.UdpIn <- inbound.NewPacket(target, packet, h.Type):
|
||||
default:
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *ListenerHandler) NewError(ctx context.Context, err error) {
|
||||
log.Warnln("%s listener get error: %+v", h.Type.String(), err)
|
||||
}
|
||||
|
||||
type packet struct {
|
||||
conn *network.PacketConn
|
||||
mutex *sync.Mutex
|
||||
rAddr net.Addr
|
||||
lAddr net.Addr
|
||||
buff *buf.Buffer
|
||||
}
|
||||
|
||||
func (c *packet) Data() []byte {
|
||||
return c.buff.Bytes()
|
||||
}
|
||||
|
||||
// WriteBack wirtes UDP packet with source(ip, port) = `addr`
|
||||
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||
if addr == nil {
|
||||
err = errors.New("address is invalid")
|
||||
return
|
||||
}
|
||||
buff := buf.NewPacket()
|
||||
defer buff.Release()
|
||||
n, err = buff.Write(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
conn := *c.conn
|
||||
if conn == nil {
|
||||
err = errors.New("writeBack to closed connection")
|
||||
return
|
||||
}
|
||||
err = conn.WritePacket(buff, M.ParseSocksaddr(addr.String()))
|
||||
return
|
||||
}
|
||||
|
||||
// LocalAddr returns the source IP/Port of UDP Packet
|
||||
func (c *packet) LocalAddr() net.Addr {
|
||||
return c.rAddr
|
||||
}
|
||||
|
||||
func (c *packet) Drop() {
|
||||
c.buff.Release()
|
||||
}
|
||||
|
||||
func (c *packet) InAddr() net.Addr {
|
||||
return c.lAddr
|
||||
}
|
165
listener/sing_tun/dns.go
Normal file
165
listener/sing_tun/dns.go
Normal file
@ -0,0 +1,165 @@
|
||||
package sing_tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/Dreamacro/clash/listener/sing"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
const DefaultDnsReadTimeout = time.Second * 10
|
||||
|
||||
type ListenerHandler struct {
|
||||
sing.ListenerHandler
|
||||
DnsAdds []netip.AddrPort
|
||||
}
|
||||
|
||||
func (h *ListenerHandler) ShouldHijackDns(targetAddr netip.AddrPort) bool {
|
||||
if targetAddr.Addr().IsLoopback() && targetAddr.Port() == 53 { // cause by system stack
|
||||
return true
|
||||
}
|
||||
for _, addrPort := range h.DnsAdds {
|
||||
if addrPort == targetAddr || (addrPort.Addr().IsUnspecified() && targetAddr.Port() == 53) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
if h.ShouldHijackDns(metadata.Destination.AddrPort()) {
|
||||
log.Debugln("[DNS] hijack tcp:%s", metadata.Destination.String())
|
||||
buff := pool.Get(pool.UDPBufferSize)
|
||||
defer func() {
|
||||
_ = pool.Put(buff)
|
||||
_ = conn.Close()
|
||||
}()
|
||||
for {
|
||||
if conn.SetReadDeadline(time.Now().Add(DefaultDnsReadTimeout)) != nil {
|
||||
break
|
||||
}
|
||||
|
||||
length := uint16(0)
|
||||
if err := binary.Read(conn, binary.BigEndian, &length); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if int(length) > len(buff) {
|
||||
break
|
||||
}
|
||||
|
||||
n, err := io.ReadFull(conn, buff[:length])
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
err = func() error {
|
||||
inData := buff[:n]
|
||||
msg, err := RelayDnsPacket(inData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = binary.Write(conn, binary.BigEndian, uint16(len(msg)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Write(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return h.ListenerHandler.NewConnection(ctx, conn, metadata)
|
||||
}
|
||||
|
||||
func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata M.Metadata) error {
|
||||
if h.ShouldHijackDns(metadata.Destination.AddrPort()) {
|
||||
log.Debugln("[DNS] hijack udp:%s from %s", metadata.Destination.String(), metadata.Source.String())
|
||||
defer func() { _ = conn.Close() }()
|
||||
mutex := sync.Mutex{}
|
||||
conn2 := conn // a new interface to set nil in defer
|
||||
defer func() {
|
||||
mutex.Lock() // this goroutine must exit after all conn.WritePacket() is not running
|
||||
defer mutex.Unlock()
|
||||
conn2 = nil
|
||||
}()
|
||||
for {
|
||||
buff := buf.NewPacket()
|
||||
dest, err := conn.ReadPacket(buff)
|
||||
if err != nil {
|
||||
buff.Release()
|
||||
if E.IsClosed(err) {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
inData := buff.Bytes()
|
||||
msg, err := RelayDnsPacket(inData)
|
||||
if err != nil {
|
||||
buff.Release()
|
||||
return
|
||||
}
|
||||
buff.Reset()
|
||||
_, err = buff.Write(msg)
|
||||
if err != nil {
|
||||
buff.Release()
|
||||
return
|
||||
}
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
conn := conn2
|
||||
if conn == nil {
|
||||
return
|
||||
}
|
||||
err = conn.WritePacket(buff, dest) // WritePacket will release buff
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return h.ListenerHandler.NewPacketConnection(ctx, conn, metadata)
|
||||
}
|
||||
|
||||
func RelayDnsPacket(payload []byte) ([]byte, error) {
|
||||
msg := &D.Msg{}
|
||||
if err := msg.Unpack(payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, err := resolver.ServeMsg(msg)
|
||||
if err != nil {
|
||||
m := new(D.Msg)
|
||||
m.SetRcode(msg, D.RcodeServerFailure)
|
||||
return m.Pack()
|
||||
}
|
||||
|
||||
r.SetRcode(msg, r.Rcode)
|
||||
r.Compress = true
|
||||
return r.Pack()
|
||||
}
|
284
listener/sing_tun/server.go
Normal file
284
listener/sing_tun/server.go
Normal file
@ -0,0 +1,284 @@
|
||||
package sing_tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/listener/sing"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/ranges"
|
||||
)
|
||||
|
||||
var InterfaceName = "Meta"
|
||||
|
||||
type Listener struct {
|
||||
closed bool
|
||||
options config.Tun
|
||||
handler *ListenerHandler
|
||||
tunName string
|
||||
|
||||
tunIf tun.Tun
|
||||
tunStack tun.Stack
|
||||
|
||||
networkUpdateMonitor tun.NetworkUpdateMonitor
|
||||
defaultInterfaceMonitor tun.DefaultInterfaceMonitor
|
||||
packageManager tun.PackageManager
|
||||
}
|
||||
|
||||
func CalculateInterfaceName(name string) (tunName string) {
|
||||
if runtime.GOOS == "darwin" {
|
||||
tunName = "utun"
|
||||
} else if name != "" {
|
||||
tunName = name
|
||||
return
|
||||
} else {
|
||||
tunName = "tun"
|
||||
}
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var tunIndex int
|
||||
for _, netInterface := range interfaces {
|
||||
if strings.HasPrefix(netInterface.Name, tunName) {
|
||||
index, parseErr := strconv.ParseInt(netInterface.Name[len(tunName):], 10, 16)
|
||||
if parseErr == nil {
|
||||
tunIndex = int(index) + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
tunName = F.ToString(tunName, tunIndex)
|
||||
return
|
||||
}
|
||||
|
||||
func New(options config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (l *Listener, err error) {
|
||||
tunName := options.Device
|
||||
if tunName == "" {
|
||||
tunName = CalculateInterfaceName(InterfaceName)
|
||||
}
|
||||
tunMTU := options.MTU
|
||||
if tunMTU == 0 {
|
||||
tunMTU = 9000
|
||||
}
|
||||
var udpTimeout int64
|
||||
if options.UDPTimeout != 0 {
|
||||
udpTimeout = options.UDPTimeout
|
||||
} else {
|
||||
udpTimeout = int64(sing.UDPTimeout.Seconds())
|
||||
}
|
||||
includeUID := uidToRange(options.IncludeUID)
|
||||
if len(options.IncludeUIDRange) > 0 {
|
||||
var err error
|
||||
includeUID, err = parseRange(includeUID, options.IncludeUIDRange)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse include_uid_range")
|
||||
}
|
||||
}
|
||||
excludeUID := uidToRange(options.ExcludeUID)
|
||||
if len(options.ExcludeUIDRange) > 0 {
|
||||
var err error
|
||||
excludeUID, err = parseRange(excludeUID, options.ExcludeUIDRange)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse exclude_uid_range")
|
||||
}
|
||||
}
|
||||
|
||||
var dnsAdds []netip.AddrPort
|
||||
|
||||
for _, d := range options.DNSHijack {
|
||||
dnsAdds = append(dnsAdds, d)
|
||||
}
|
||||
for _, a := range options.Inet4Address {
|
||||
addrPort := netip.AddrPortFrom(a.Build().Addr().Next(), 53)
|
||||
dnsAdds = append(dnsAdds, addrPort)
|
||||
}
|
||||
for _, a := range options.Inet6Address {
|
||||
addrPort := netip.AddrPortFrom(a.Build().Addr().Next(), 53)
|
||||
dnsAdds = append(dnsAdds, addrPort)
|
||||
}
|
||||
|
||||
handler := &ListenerHandler{
|
||||
ListenerHandler: sing.ListenerHandler{
|
||||
TcpIn: tcpIn,
|
||||
UdpIn: udpIn,
|
||||
Type: C.TUN,
|
||||
},
|
||||
DnsAdds: dnsAdds,
|
||||
}
|
||||
l = &Listener{
|
||||
closed: false,
|
||||
options: options,
|
||||
handler: handler,
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
l.Close()
|
||||
l = nil
|
||||
}
|
||||
}()
|
||||
|
||||
networkUpdateMonitor, err := tun.NewNetworkUpdateMonitor(handler)
|
||||
if err != nil {
|
||||
err = E.Cause(err, "create NetworkUpdateMonitor")
|
||||
return
|
||||
}
|
||||
l.networkUpdateMonitor = networkUpdateMonitor
|
||||
err = networkUpdateMonitor.Start()
|
||||
if err != nil {
|
||||
err = E.Cause(err, "start NetworkUpdateMonitor")
|
||||
return
|
||||
}
|
||||
|
||||
defaultInterfaceMonitor, err := tun.NewDefaultInterfaceMonitor(networkUpdateMonitor, tun.DefaultInterfaceMonitorOptions{OverrideAndroidVPN: true})
|
||||
if err != nil {
|
||||
err = E.Cause(err, "create DefaultInterfaceMonitor")
|
||||
return
|
||||
}
|
||||
l.defaultInterfaceMonitor = defaultInterfaceMonitor
|
||||
defaultInterfaceMonitor.RegisterCallback(func(event int) error {
|
||||
l.FlushDefaultInterface()
|
||||
return nil
|
||||
})
|
||||
err = defaultInterfaceMonitor.Start()
|
||||
if err != nil {
|
||||
err = E.Cause(err, "start DefaultInterfaceMonitor")
|
||||
return
|
||||
}
|
||||
|
||||
tunOptions := tun.Options{
|
||||
Name: tunName,
|
||||
MTU: tunMTU,
|
||||
Inet4Address: common.Map(options.Inet4Address, config.ListenPrefix.Build),
|
||||
Inet6Address: common.Map(options.Inet6Address, config.ListenPrefix.Build),
|
||||
AutoRoute: options.AutoRoute,
|
||||
StrictRoute: options.StrictRoute,
|
||||
Inet4RouteAddress: common.Map(options.Inet4RouteAddress, config.ListenPrefix.Build),
|
||||
Inet6RouteAddress: common.Map(options.Inet6RouteAddress, config.ListenPrefix.Build),
|
||||
IncludeUID: includeUID,
|
||||
ExcludeUID: excludeUID,
|
||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||
IncludePackage: options.IncludePackage,
|
||||
ExcludePackage: options.ExcludePackage,
|
||||
InterfaceMonitor: defaultInterfaceMonitor,
|
||||
TableIndex: 2022,
|
||||
}
|
||||
|
||||
err = l.buildAndroidRules(&tunOptions)
|
||||
if err != nil {
|
||||
err = E.Cause(err, "build android rules")
|
||||
return
|
||||
}
|
||||
tunIf, err := tunOpen(tunOptions)
|
||||
if err != nil {
|
||||
err = E.Cause(err, "configure tun interface")
|
||||
return
|
||||
}
|
||||
l.tunIf = tunIf
|
||||
l.tunStack, err = tun.NewStack(strings.ToLower(options.Stack.String()), tun.StackOptions{
|
||||
Context: context.TODO(),
|
||||
Tun: tunIf,
|
||||
MTU: tunOptions.MTU,
|
||||
Name: tunOptions.Name,
|
||||
Inet4Address: tunOptions.Inet4Address,
|
||||
Inet6Address: tunOptions.Inet6Address,
|
||||
EndpointIndependentNat: options.EndpointIndependentNat,
|
||||
UDPTimeout: udpTimeout,
|
||||
Handler: handler,
|
||||
Logger: sing.Logger,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = l.tunStack.Start()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Infoln("Tun adapter listening at: %s(%s,%s), mtu: %d, auto route: %v, ip stack: %s",
|
||||
tunName, tunOptions.Inet4Address, tunOptions.Inet6Address, tunMTU, options.AutoRoute, options.Stack)
|
||||
return
|
||||
}
|
||||
|
||||
func (l *Listener) FlushDefaultInterface() {
|
||||
if l.options.AutoDetectInterface {
|
||||
targetInterface := dialer.DefaultInterface.Load()
|
||||
for _, destination := range []netip.Addr{netip.IPv4Unspecified(), netip.IPv6Unspecified(), netip.MustParseAddr("1.1.1.1")} {
|
||||
autoDetectInterfaceName := l.defaultInterfaceMonitor.DefaultInterfaceName(destination)
|
||||
if autoDetectInterfaceName == l.tunName {
|
||||
log.Warnln("Auto detect interface by %s get same name with tun", destination.String())
|
||||
} else if autoDetectInterfaceName == "" || autoDetectInterfaceName == "<nil>" {
|
||||
log.Warnln("Auto detect interface by %s get empty name.", destination.String())
|
||||
} else {
|
||||
targetInterface = autoDetectInterfaceName
|
||||
if old := dialer.DefaultInterface.Load(); old != targetInterface {
|
||||
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, targetInterface)
|
||||
|
||||
dialer.DefaultInterface.Store(targetInterface)
|
||||
|
||||
iface.FlushCache()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func uidToRange(uidList []uint32) []ranges.Range[uint32] {
|
||||
return common.Map(uidList, func(uid uint32) ranges.Range[uint32] {
|
||||
return ranges.NewSingle(uid)
|
||||
})
|
||||
}
|
||||
|
||||
func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges.Range[uint32], error) {
|
||||
for _, uidRange := range rangeList {
|
||||
if !strings.Contains(uidRange, ":") {
|
||||
return nil, E.New("missing ':' in range: ", uidRange)
|
||||
}
|
||||
subIndex := strings.Index(uidRange, ":")
|
||||
if subIndex == 0 {
|
||||
return nil, E.New("missing range start: ", uidRange)
|
||||
} else if subIndex == len(uidRange)-1 {
|
||||
return nil, E.New("missing range end: ", uidRange)
|
||||
}
|
||||
var start, end uint64
|
||||
var err error
|
||||
start, err = strconv.ParseUint(uidRange[:subIndex], 10, 32)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse range start")
|
||||
}
|
||||
end, err = strconv.ParseUint(uidRange[subIndex+1:], 10, 32)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse range end")
|
||||
}
|
||||
uidRanges = append(uidRanges, ranges.New(uint32(start), uint32(end)))
|
||||
}
|
||||
return uidRanges, nil
|
||||
}
|
||||
|
||||
func (l *Listener) Close() {
|
||||
l.closed = true
|
||||
_ = common.Close(
|
||||
l.tunStack,
|
||||
l.tunIf,
|
||||
l.defaultInterfaceMonitor,
|
||||
l.networkUpdateMonitor,
|
||||
l.packageManager,
|
||||
)
|
||||
}
|
||||
|
||||
func (l *Listener) Config() config.Tun {
|
||||
return l.options
|
||||
}
|
23
listener/sing_tun/server_android.go
Normal file
23
listener/sing_tun/server_android.go
Normal file
@ -0,0 +1,23 @@
|
||||
package sing_tun
|
||||
|
||||
import (
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
)
|
||||
|
||||
func (l *Listener) buildAndroidRules(tunOptions *tun.Options) error {
|
||||
packageManager, err := tun.NewPackageManager(l.handler)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = packageManager.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.packageManager = packageManager
|
||||
tunOptions.BuildAndroidRules(packageManager, l.handler)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *ListenerHandler) OnPackagesUpdated(packages int, sharedUsers int) {
|
||||
return
|
||||
}
|
11
listener/sing_tun/server_notandroid.go
Normal file
11
listener/sing_tun/server_notandroid.go
Normal file
@ -0,0 +1,11 @@
|
||||
//go:build !android
|
||||
|
||||
package sing_tun
|
||||
|
||||
import (
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
)
|
||||
|
||||
func (l *Listener) buildAndroidRules(tunOptions *tun.Options) error {
|
||||
return nil
|
||||
}
|
11
listener/sing_tun/server_notwindows.go
Normal file
11
listener/sing_tun/server_notwindows.go
Normal file
@ -0,0 +1,11 @@
|
||||
//go:build !windows
|
||||
|
||||
package sing_tun
|
||||
|
||||
import (
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
)
|
||||
|
||||
func tunOpen(options tun.Options) (tun.Tun, error) {
|
||||
return tun.Open(options)
|
||||
}
|
30
listener/sing_tun/server_windows.go
Normal file
30
listener/sing_tun/server_windows.go
Normal file
@ -0,0 +1,30 @@
|
||||
package sing_tun
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
)
|
||||
|
||||
func tunOpen(options tun.Options) (tunIf tun.Tun, err error) {
|
||||
maxRetry := 3
|
||||
for i := 0; i < maxRetry; i++ {
|
||||
timeBegin := time.Now()
|
||||
tunIf, err = tun.Open(options)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
timeEnd := time.Now()
|
||||
if timeEnd.Sub(timeBegin) < 1*time.Second { // retrying for "Cannot create a file when that file already exists."
|
||||
return
|
||||
}
|
||||
log.Warnln("Start Tun interface timeout: %s [retrying %d/%d]", err, i+1, maxRetry)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func init() {
|
||||
tun.TunnelType = InterfaceName
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
//go:build !no_gvisor
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
// Device is the interface that implemented by network layer devices (e.g. tun),
|
||||
// and easy to use as stack.LinkEndpoint.
|
||||
type Device interface {
|
||||
stack.LinkEndpoint
|
||||
|
||||
// Name returns the current name of the device.
|
||||
Name() string
|
||||
|
||||
// Type returns the driver type of the device.
|
||||
Type() string
|
||||
|
||||
// Read packets from tun device
|
||||
Read(packet []byte) (int, error)
|
||||
|
||||
// Write packets to tun device
|
||||
Write(packet []byte) (int, error)
|
||||
|
||||
// Close stops and closes the device.
|
||||
Close() error
|
||||
|
||||
// UseEndpoint work for gVisor stack
|
||||
UseEndpoint() error
|
||||
|
||||
// UseIOBased work for other ip stack
|
||||
UseIOBased() error
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
//go:build no_gvisor
|
||||
|
||||
package device
|
||||
|
||||
// Device is the interface that implemented by network layer devices (e.g. tun),
|
||||
// and easy to use as stack.LinkEndpoint.
|
||||
type Device interface {
|
||||
|
||||
// Name returns the current name of the device.
|
||||
Name() string
|
||||
|
||||
// Type returns the driver type of the device.
|
||||
Type() string
|
||||
|
||||
// Read packets from tun device
|
||||
Read(packet []byte) (int, error)
|
||||
|
||||
// Write packets to tun device
|
||||
Write(packet []byte) (int, error)
|
||||
|
||||
// Close stops and closes the device.
|
||||
Close() error
|
||||
|
||||
// UseEndpoint work for gVisor stack
|
||||
UseEndpoint() error
|
||||
|
||||
// UseIOBased work for other ip stack
|
||||
UseIOBased() error
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package fdbased
|
||||
|
||||
const Driver = "fd"
|
||||
|
||||
const defaultMTU = 1500
|
@ -1,49 +0,0 @@
|
||||
//go:build !windows
|
||||
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func Open(name string, mtu uint32) (device.Device, error) {
|
||||
fd, err := strconv.Atoi(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot open fd: %s", name)
|
||||
}
|
||||
if mtu == 0 {
|
||||
mtu = defaultMTU
|
||||
}
|
||||
return open(fd, mtu)
|
||||
}
|
||||
|
||||
func (f *FD) Type() string {
|
||||
return Driver
|
||||
}
|
||||
|
||||
func (f *FD) Name() string {
|
||||
return strconv.Itoa(f.fd)
|
||||
}
|
||||
|
||||
func (f *FD) Close() error {
|
||||
err := unix.Close(f.fd)
|
||||
if f.file != nil {
|
||||
_ = f.file.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *FD) UseEndpoint() error {
|
||||
return f.useEndpoint()
|
||||
}
|
||||
|
||||
func (f *FD) UseIOBased() error {
|
||||
return f.useIOBased()
|
||||
}
|
||||
|
||||
var _ device.Device = (*FD)(nil)
|
@ -1,17 +0,0 @@
|
||||
//go:build !no_gvisor
|
||||
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
"os"
|
||||
)
|
||||
|
||||
type FD struct {
|
||||
stack.LinkEndpoint
|
||||
|
||||
fd int
|
||||
mtu uint32
|
||||
|
||||
file *os.File
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
//go:build no_gvisor
|
||||
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
type FD struct {
|
||||
fd int
|
||||
mtu uint32
|
||||
|
||||
file *os.File
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
)
|
||||
|
||||
func Open(name string, mtu uint32) (device.Device, error) {
|
||||
return nil, errors.New("not supported")
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
)
|
||||
|
||||
func open(fd int, mtu uint32) (device.Device, error) {
|
||||
f := &FD{fd: fd, mtu: mtu}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (f *FD) useEndpoint() error {
|
||||
return f.newLinuxEp()
|
||||
}
|
||||
|
||||
func (f *FD) useIOBased() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FD) Read(packet []byte) (int, error) {
|
||||
return f.read(packet)
|
||||
}
|
||||
|
||||
func (f *FD) Write(packet []byte) (int, error) {
|
||||
return f.write(packet)
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
//go:build !no_gvisor && (linux || android)
|
||||
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/rawfile"
|
||||
)
|
||||
|
||||
func (f *FD) newLinuxEp() error {
|
||||
ep, err := fdbased.New(&fdbased.Options{
|
||||
FDs: []int{f.fd},
|
||||
MTU: f.mtu,
|
||||
// TUN only, ignore ethernet header.
|
||||
EthernetHeader: false,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("create endpoint: %w", err)
|
||||
}
|
||||
f.LinkEndpoint = ep
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FD) read(packet []byte) (int, error) {
|
||||
n, gvErr := rawfile.BlockingRead(f.fd, packet)
|
||||
if gvErr != nil {
|
||||
return 0, fmt.Errorf("read error: %s", gvErr.String())
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (f *FD) write(packet []byte) (int, error) {
|
||||
n := len(packet)
|
||||
if n == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
gvErr := rawfile.NonBlockingWrite(f.fd, packet)
|
||||
if gvErr != nil {
|
||||
return 0, fmt.Errorf("write error: %s", gvErr.String())
|
||||
}
|
||||
return n, nil
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
//go:build no_gvisor
|
||||
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (f *FD) newLinuxEp() error {
|
||||
return fmt.Errorf("unsupported gvisor on the build")
|
||||
}
|
||||
|
||||
func (f *FD) read(packet []byte) (int, error) {
|
||||
return 0, fmt.Errorf("unsupported gvisor on the build")
|
||||
}
|
||||
|
||||
func (f *FD) write(packet []byte) (int, error) {
|
||||
return 0, fmt.Errorf("unsupported gvisor on the build")
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
//go:build !linux && !windows
|
||||
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
)
|
||||
|
||||
func open(fd int, mtu uint32) (device.Device, error) {
|
||||
f := &FD{fd: fd, mtu: mtu}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (f *FD) useEndpoint() error {
|
||||
return f.newEpOther()
|
||||
}
|
||||
|
||||
func (f *FD) useIOBased() error {
|
||||
f.file = os.NewFile(uintptr(f.fd), f.Name())
|
||||
if f.file == nil {
|
||||
return fmt.Errorf("create IOBased failed, can not open file: %s", f.Name())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FD) Read(packet []byte) (int, error) {
|
||||
return f.file.Read(packet)
|
||||
}
|
||||
|
||||
func (f *FD) Write(packet []byte) (int, error) {
|
||||
return f.file.Write(packet)
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
//go:build !no_gvisor && !linux && !windows
|
||||
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/Dreamacro/clash/listener/tun/device/iobased"
|
||||
)
|
||||
|
||||
func (f *FD) newEpOther() error {
|
||||
ep, err := iobased.New(os.NewFile(uintptr(f.fd), f.Name()), f.mtu, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create endpoint: %w", err)
|
||||
}
|
||||
f.LinkEndpoint = ep
|
||||
return nil
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
//go:build no_gvisor && !linux && !windows
|
||||
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (f *FD) newEpOther() error {
|
||||
return fmt.Errorf("unsupported gvisor on the build")
|
||||
}
|
@ -1,166 +0,0 @@
|
||||
//go:build !no_gvisor
|
||||
|
||||
// Package iobased provides the implementation of io.ReadWriter
|
||||
// based data-link layer endpoints.
|
||||
package iobased
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"gvisor.dev/gvisor/pkg/bufferv2"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
const (
|
||||
// Queue length for outbound packet, arriving for read. Overflow
|
||||
// causes packet drops.
|
||||
defaultOutQueueLen = 1 << 10
|
||||
)
|
||||
|
||||
// Endpoint implements the interface of stack.LinkEndpoint from io.ReadWriter.
|
||||
type Endpoint struct {
|
||||
*channel.Endpoint
|
||||
|
||||
// rw is the io.ReadWriter for reading and writing packets.
|
||||
rw io.ReadWriter
|
||||
|
||||
// mtu (maximum transmission unit) is the maximum size of a packet.
|
||||
mtu uint32
|
||||
|
||||
// offset can be useful when perform TUN device I/O with TUN_PI enabled.
|
||||
offset int
|
||||
|
||||
// once is used to perform the init action once when attaching.
|
||||
once sync.Once
|
||||
|
||||
// wg keeps track of running goroutines.
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// New returns stack.LinkEndpoint(.*Endpoint) and error.
|
||||
func New(rw io.ReadWriter, mtu uint32, offset int) (*Endpoint, error) {
|
||||
if mtu == 0 {
|
||||
return nil, errors.New("MTU size is zero")
|
||||
}
|
||||
|
||||
if rw == nil {
|
||||
return nil, errors.New("RW interface is nil")
|
||||
}
|
||||
|
||||
if offset < 0 {
|
||||
return nil, errors.New("offset must be non-negative")
|
||||
}
|
||||
|
||||
return &Endpoint{
|
||||
Endpoint: channel.New(defaultOutQueueLen, mtu, ""),
|
||||
rw: rw,
|
||||
mtu: mtu,
|
||||
offset: offset,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *Endpoint) Wait() {
|
||||
e.wg.Wait()
|
||||
}
|
||||
|
||||
// Attach launches the goroutine that reads packets from io.Reader and
|
||||
// dispatches them via the provided dispatcher.
|
||||
func (e *Endpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
e.Endpoint.Attach(dispatcher)
|
||||
e.once.Do(func() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
e.wg.Add(2)
|
||||
go func() {
|
||||
e.outboundLoop(ctx)
|
||||
e.wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
e.dispatchLoop(cancel)
|
||||
e.wg.Done()
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
// dispatchLoop dispatches packets to upper layer.
|
||||
func (e *Endpoint) dispatchLoop(cancel context.CancelFunc) {
|
||||
// Call cancel() to ensure (*Endpoint).outboundLoop(context.Context) exits
|
||||
// gracefully after (*Endpoint).dispatchLoop(context.CancelFunc) returns.
|
||||
defer cancel()
|
||||
|
||||
mtu := int(e.mtu)
|
||||
for {
|
||||
data := pool.Get(mtu)
|
||||
|
||||
n, err := e.rw.Read(data)
|
||||
if err != nil {
|
||||
_ = pool.Put(data)
|
||||
break
|
||||
}
|
||||
|
||||
if n == 0 || n > mtu {
|
||||
_ = pool.Put(data)
|
||||
continue
|
||||
}
|
||||
|
||||
if !e.IsAttached() {
|
||||
_ = pool.Put(data)
|
||||
continue /* unattached, drop packet */
|
||||
}
|
||||
|
||||
var p tcpip.NetworkProtocolNumber
|
||||
switch header.IPVersion(data) {
|
||||
case header.IPv4Version:
|
||||
p = header.IPv4ProtocolNumber
|
||||
case header.IPv6Version:
|
||||
p = header.IPv6ProtocolNumber
|
||||
default:
|
||||
_ = pool.Put(data)
|
||||
continue
|
||||
}
|
||||
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Payload: bufferv2.MakeWithData(data),
|
||||
OnRelease: func() {
|
||||
_ = pool.Put(data)
|
||||
},
|
||||
})
|
||||
|
||||
e.InjectInbound(p, pkt)
|
||||
|
||||
pkt.DecRef()
|
||||
}
|
||||
}
|
||||
|
||||
// outboundLoop reads outbound packets from channel, and then it calls
|
||||
// writePacket to send those packets back to lower layer.
|
||||
func (e *Endpoint) outboundLoop(ctx context.Context) {
|
||||
for {
|
||||
pkt := e.ReadContext(ctx)
|
||||
if pkt == nil {
|
||||
break
|
||||
}
|
||||
e.writePacket(pkt)
|
||||
}
|
||||
}
|
||||
|
||||
// writePacket writes outbound packets to the io.Writer.
|
||||
func (e *Endpoint) writePacket(pkt *stack.PacketBuffer) tcpip.Error {
|
||||
pktView := pkt.ToView()
|
||||
|
||||
defer func() {
|
||||
pktView.Release()
|
||||
pkt.DecRef()
|
||||
}()
|
||||
|
||||
if _, err := e.rw.Write(pktView.AsSlice()); err != nil {
|
||||
return &tcpip.ErrInvalidEndpointState{}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1 +0,0 @@
|
||||
package iobased
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,233 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.zx2c4.com/wireguard/windows/driver/memmod"
|
||||
)
|
||||
|
||||
//go:linkname modwintun golang.zx2c4.com/wintun.modwintun
|
||||
|
||||
//go:linkname procWintunCreateAdapter golang.zx2c4.com/wintun.procWintunCreateAdapter
|
||||
|
||||
//go:linkname procWintunOpenAdapter golang.zx2c4.com/wintun.procWintunOpenAdapter
|
||||
|
||||
//go:linkname procWintunCloseAdapter golang.zx2c4.com/wintun.procWintunCloseAdapter
|
||||
|
||||
//go:linkname procWintunDeleteDriver golang.zx2c4.com/wintun.procWintunDeleteDriver
|
||||
|
||||
//go:linkname procWintunGetAdapterLUID golang.zx2c4.com/wintun.procWintunGetAdapterLUID
|
||||
|
||||
//go:linkname procWintunGetRunningDriverVersion golang.zx2c4.com/wintun.procWintunGetRunningDriverVersion
|
||||
|
||||
//go:linkname procWintunAllocateSendPacket golang.zx2c4.com/wintun.procWintunAllocateSendPacket
|
||||
|
||||
//go:linkname procWintunEndSession golang.zx2c4.com/wintun.procWintunEndSession
|
||||
|
||||
//go:linkname procWintunGetReadWaitEvent golang.zx2c4.com/wintun.procWintunGetReadWaitEvent
|
||||
|
||||
//go:linkname procWintunReceivePacket golang.zx2c4.com/wintun.procWintunReceivePacket
|
||||
|
||||
//go:linkname procWintunReleaseReceivePacket golang.zx2c4.com/wintun.procWintunReleaseReceivePacket
|
||||
|
||||
//go:linkname procWintunSendPacket golang.zx2c4.com/wintun.procWintunSendPacket
|
||||
|
||||
//go:linkname procWintunStartSession golang.zx2c4.com/wintun.procWintunStartSession
|
||||
|
||||
var (
|
||||
modwintun *lazyDLL
|
||||
procWintunCreateAdapter *lazyProc
|
||||
procWintunOpenAdapter *lazyProc
|
||||
procWintunCloseAdapter *lazyProc
|
||||
procWintunDeleteDriver *lazyProc
|
||||
procWintunGetAdapterLUID *lazyProc
|
||||
procWintunGetRunningDriverVersion *lazyProc
|
||||
procWintunAllocateSendPacket *lazyProc
|
||||
procWintunEndSession *lazyProc
|
||||
procWintunGetReadWaitEvent *lazyProc
|
||||
procWintunReceivePacket *lazyProc
|
||||
procWintunReleaseReceivePacket *lazyProc
|
||||
procWintunSendPacket *lazyProc
|
||||
procWintunStartSession *lazyProc
|
||||
)
|
||||
|
||||
type loggerLevel int
|
||||
|
||||
const (
|
||||
logInfo loggerLevel = iota
|
||||
logWarn
|
||||
logErr
|
||||
)
|
||||
|
||||
func init() {
|
||||
modwintun = newLazyDLL("wintun.dll", setupLogger)
|
||||
procWintunCreateAdapter = modwintun.NewProc("WintunCreateAdapter")
|
||||
procWintunOpenAdapter = modwintun.NewProc("WintunOpenAdapter")
|
||||
procWintunCloseAdapter = modwintun.NewProc("WintunCloseAdapter")
|
||||
procWintunDeleteDriver = modwintun.NewProc("WintunDeleteDriver")
|
||||
procWintunGetAdapterLUID = modwintun.NewProc("WintunGetAdapterLUID")
|
||||
procWintunGetRunningDriverVersion = modwintun.NewProc("WintunGetRunningDriverVersion")
|
||||
procWintunAllocateSendPacket = modwintun.NewProc("WintunAllocateSendPacket")
|
||||
procWintunEndSession = modwintun.NewProc("WintunEndSession")
|
||||
procWintunGetReadWaitEvent = modwintun.NewProc("WintunGetReadWaitEvent")
|
||||
procWintunReceivePacket = modwintun.NewProc("WintunReceivePacket")
|
||||
procWintunReleaseReceivePacket = modwintun.NewProc("WintunReleaseReceivePacket")
|
||||
procWintunSendPacket = modwintun.NewProc("WintunSendPacket")
|
||||
procWintunStartSession = modwintun.NewProc("WintunStartSession")
|
||||
}
|
||||
|
||||
func InitWintun() (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("init wintun.dll error: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
if err = modwintun.Load(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
procWintunCreateAdapter.Addr()
|
||||
procWintunOpenAdapter.Addr()
|
||||
procWintunCloseAdapter.Addr()
|
||||
procWintunDeleteDriver.Addr()
|
||||
procWintunGetAdapterLUID.Addr()
|
||||
procWintunGetRunningDriverVersion.Addr()
|
||||
procWintunAllocateSendPacket.Addr()
|
||||
procWintunEndSession.Addr()
|
||||
procWintunGetReadWaitEvent.Addr()
|
||||
procWintunReceivePacket.Addr()
|
||||
procWintunReleaseReceivePacket.Addr()
|
||||
procWintunSendPacket.Addr()
|
||||
procWintunStartSession.Addr()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func newLazyDLL(name string, onLoad func(d *lazyDLL)) *lazyDLL {
|
||||
return &lazyDLL{Name: name, onLoad: onLoad}
|
||||
}
|
||||
|
||||
func logMessage(level loggerLevel, _ uint64, msg *uint16) int {
|
||||
switch level {
|
||||
case logInfo:
|
||||
log.Infoln("[TUN] %s", windows.UTF16PtrToString(msg))
|
||||
case logWarn:
|
||||
log.Warnln("[TUN] %s", windows.UTF16PtrToString(msg))
|
||||
case logErr:
|
||||
log.Errorln("[TUN] %s", windows.UTF16PtrToString(msg))
|
||||
default:
|
||||
log.Debugln("[TUN] %s", windows.UTF16PtrToString(msg))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func setupLogger(dll *lazyDLL) {
|
||||
var callback uintptr
|
||||
if runtime.GOARCH == "386" {
|
||||
callback = windows.NewCallback(func(level loggerLevel, _, _ uint32, msg *uint16) int {
|
||||
return logMessage(level, 0, msg)
|
||||
})
|
||||
} else if runtime.GOARCH == "arm" {
|
||||
callback = windows.NewCallback(func(level loggerLevel, _, _, _ uint32, msg *uint16) int {
|
||||
return logMessage(level, 0, msg)
|
||||
})
|
||||
} else if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" {
|
||||
callback = windows.NewCallback(logMessage)
|
||||
}
|
||||
_, _, _ = syscall.SyscallN(dll.NewProc("WintunSetLogger").Addr(), callback)
|
||||
}
|
||||
|
||||
func (d *lazyDLL) NewProc(name string) *lazyProc {
|
||||
return &lazyProc{dll: d, Name: name}
|
||||
}
|
||||
|
||||
type lazyProc struct {
|
||||
Name string
|
||||
mu sync.Mutex
|
||||
dll *lazyDLL
|
||||
addr uintptr
|
||||
}
|
||||
|
||||
func (p *lazyProc) Find() error {
|
||||
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr))) != nil {
|
||||
return nil
|
||||
}
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.addr != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := p.dll.Load()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading DLL: %s, MODULE: %s, error: %w", p.dll.Name, p.Name, err)
|
||||
}
|
||||
addr, err := p.nameToAddr()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting %s address: %w", p.Name, err)
|
||||
}
|
||||
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr)), unsafe.Pointer(addr))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *lazyProc) Addr() uintptr {
|
||||
err := p.Find()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return p.addr
|
||||
}
|
||||
|
||||
func (p *lazyProc) Load() error {
|
||||
return p.dll.Load()
|
||||
}
|
||||
|
||||
type lazyDLL struct {
|
||||
Name string
|
||||
Base windows.Handle
|
||||
mu sync.Mutex
|
||||
module *memmod.Module
|
||||
onLoad func(d *lazyDLL)
|
||||
}
|
||||
|
||||
func (d *lazyDLL) Load() error {
|
||||
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.module))) != nil {
|
||||
return nil
|
||||
}
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
if d.module != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
module, err := memmod.LoadLibrary(dllContent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load library: %w", err)
|
||||
}
|
||||
d.Base = windows.Handle(module.BaseAddr())
|
||||
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.module)), unsafe.Pointer(module))
|
||||
if d.onLoad != nil {
|
||||
d.onLoad(d)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *lazyProc) nameToAddr() (uintptr, error) {
|
||||
return p.dll.module.ProcAddressByName(p.Name)
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed x86/wintun.dll
|
||||
var dllContent []byte
|
@ -1,13 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed amd64/wintun.dll
|
||||
var dllContent []byte
|
@ -1,13 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed arm/wintun.dll
|
||||
var dllContent []byte
|
@ -1,13 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed arm64/wintun.dll
|
||||
var dllContent []byte
|
@ -1,10 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
// https://git.zx2c4.com/wireguard-go/tree/tun/wintun
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package driver
|
Binary file not shown.
@ -1,14 +0,0 @@
|
||||
// Package tun provides TUN which implemented device.Device interface.
|
||||
package tun
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
)
|
||||
|
||||
const Driver = "tun"
|
||||
|
||||
func (t *TUN) Type() string {
|
||||
return Driver
|
||||
}
|
||||
|
||||
var _ device.Device = (*TUN)(nil)
|
@ -1,145 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/rawfile"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/tun"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
type TUN struct {
|
||||
stack.LinkEndpoint
|
||||
|
||||
fd int
|
||||
mtu uint32
|
||||
name string
|
||||
}
|
||||
|
||||
func Open(name string, mtu uint32) (device.Device, error) {
|
||||
t := &TUN{name: name, mtu: mtu}
|
||||
|
||||
if len(t.name) >= unix.IFNAMSIZ {
|
||||
return nil, fmt.Errorf("interface name too long: %s", t.name)
|
||||
}
|
||||
|
||||
fd, err := tun.Open(t.name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create tun: %w", err)
|
||||
}
|
||||
t.fd = fd
|
||||
|
||||
if t.mtu > 0 {
|
||||
if err := setMTU(t.name, t.mtu); err != nil {
|
||||
return nil, fmt.Errorf("set mtu: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
_mtu, err := rawfile.GetMTU(t.name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get mtu: %w", err)
|
||||
}
|
||||
t.mtu = _mtu
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (t *TUN) Name() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
func (t *TUN) Read(packet []byte) (int, error) {
|
||||
n, gvErr := rawfile.BlockingRead(t.fd, packet)
|
||||
if gvErr != nil {
|
||||
return 0, fmt.Errorf("read error: %s", gvErr.String())
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (t *TUN) Write(packet []byte) (int, error) {
|
||||
n := len(packet)
|
||||
if n == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
gvErr := rawfile.NonBlockingWrite(t.fd, packet)
|
||||
if gvErr != nil {
|
||||
return 0, fmt.Errorf("write error: %s", gvErr.String())
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (t *TUN) Close() error {
|
||||
return unix.Close(t.fd)
|
||||
}
|
||||
|
||||
func (t *TUN) UseEndpoint() error {
|
||||
ep, err := fdbased.New(&fdbased.Options{
|
||||
FDs: []int{t.fd},
|
||||
MTU: t.mtu,
|
||||
// TUN only, ignore ethernet header.
|
||||
EthernetHeader: false,
|
||||
// SYS_READV support only for TUN fd.
|
||||
PacketDispatchMode: fdbased.Readv,
|
||||
// TAP/TUN fd's are not sockets and using the WritePackets calls results
|
||||
// in errors as it always defaults to using SendMMsg which is not supported
|
||||
// for tap/tun device fds.
|
||||
//
|
||||
// This CL changes WritePackets to gracefully degrade to using writev instead
|
||||
// of sendmmsg if the underlying fd is not a socket.
|
||||
//
|
||||
// Fixed: https://github.com/google/gvisor/commit/f33d034fecd7723a1e560ccc62aeeba328454fd0
|
||||
MaxSyscallHeaderBytes: 0x00,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("create endpoint: %w", err)
|
||||
}
|
||||
t.LinkEndpoint = ep
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TUN) UseIOBased() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ref: wireguard tun/tun_linux.go setMTU.
|
||||
func setMTU(name string, n uint32) error {
|
||||
// open datagram socket
|
||||
fd, err := unix.Socket(
|
||||
unix.AF_INET,
|
||||
unix.SOCK_DGRAM,
|
||||
0,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer unix.Close(fd)
|
||||
|
||||
const ifReqSize = unix.IFNAMSIZ + 64
|
||||
|
||||
// do ioctl call
|
||||
var ifr [ifReqSize]byte
|
||||
copy(ifr[:], name)
|
||||
*(*uint32)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = n
|
||||
_, _, errno := unix.Syscall(
|
||||
unix.SYS_IOCTL,
|
||||
uintptr(fd),
|
||||
uintptr(unix.SIOCSIFMTU),
|
||||
uintptr(unsafe.Pointer(&ifr[0])),
|
||||
)
|
||||
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to set MTU: %w", errno)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
//go:build !linux
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
|
||||
func Open(name string, mtu uint32) (_ device.Device, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("open tun: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
t := &TUN{
|
||||
name: name,
|
||||
mtu: mtu,
|
||||
offset: offset,
|
||||
}
|
||||
|
||||
forcedMTU := defaultMTU
|
||||
if t.mtu > 0 {
|
||||
forcedMTU = int(t.mtu)
|
||||
}
|
||||
|
||||
nt, err := newDevice(t.name, forcedMTU) // forcedMTU do not work on wintun, need to be setting by other way
|
||||
|
||||
// retry if abnormal exit at last time on Windows
|
||||
if err != nil && runtime.GOOS == "windows" && os.IsExist(err) {
|
||||
nt, err = newDevice(t.name, forcedMTU)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create tun: %w", err)
|
||||
}
|
||||
|
||||
t.nt = nt.(*tun.NativeTun)
|
||||
|
||||
tunMTU, err := nt.MTU()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get mtu: %w", err)
|
||||
}
|
||||
t.mtu = uint32(tunMTU)
|
||||
|
||||
if t.offset > 0 {
|
||||
t.cache = make([]byte, 65535)
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (t *TUN) Read(packet []byte) (int, error) {
|
||||
if t.offset == 0 {
|
||||
return t.nt.Read(packet, t.offset)
|
||||
}
|
||||
|
||||
n, err := t.nt.Read(t.cache, t.offset)
|
||||
|
||||
copy(packet, t.cache[t.offset:t.offset+n])
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (t *TUN) Write(packet []byte) (int, error) {
|
||||
if t.offset == 0 {
|
||||
return t.nt.Write(packet, t.offset)
|
||||
}
|
||||
|
||||
packet = append(t.cache[:t.offset], packet...)
|
||||
|
||||
return t.nt.Write(packet, t.offset)
|
||||
}
|
||||
|
||||
func (t *TUN) Close() error {
|
||||
defer closeIO(t)
|
||||
return t.nt.Close()
|
||||
}
|
||||
|
||||
func (t *TUN) Name() string {
|
||||
name, _ := t.nt.Name()
|
||||
return name
|
||||
}
|
||||
|
||||
func (t *TUN) UseEndpoint() error {
|
||||
return newEq(t)
|
||||
}
|
||||
|
||||
func (t *TUN) UseIOBased() error {
|
||||
return nil
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
//go:build !linux && !no_gvisor
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/listener/tun/device/iobased"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
|
||||
type TUN struct {
|
||||
*iobased.Endpoint
|
||||
nt *tun.NativeTun
|
||||
mtu uint32
|
||||
name string
|
||||
offset int
|
||||
|
||||
cache []byte
|
||||
}
|
||||
|
||||
func closeIO(t *TUN) {
|
||||
if t.Endpoint != nil {
|
||||
t.Endpoint.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func newEq(t *TUN) error {
|
||||
ep, err := iobased.New(t, t.mtu, t.offset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create endpoint: %w", err)
|
||||
}
|
||||
t.Endpoint = ep
|
||||
return nil
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
//go:build !linux && no_gvisor
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
|
||||
type TUN struct {
|
||||
nt *tun.NativeTun
|
||||
mtu uint32
|
||||
name string
|
||||
offset int
|
||||
|
||||
cache []byte
|
||||
}
|
||||
|
||||
func closeIO(t *TUN) {
|
||||
|
||||
}
|
||||
|
||||
func newEq(t *TUN) error {
|
||||
return nil
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
//go:build !linux && !windows
|
||||
|
||||
package tun
|
||||
|
||||
import "golang.zx2c4.com/wireguard/tun"
|
||||
|
||||
const (
|
||||
offset = 4 /* 4 bytes TUN_PI */
|
||||
defaultMTU = 1500
|
||||
)
|
||||
|
||||
func newDevice(name string, mtu int) (tun.Device, error) {
|
||||
return tun.CreateTUN(name, mtu)
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/listener/tun/device/tun/driver"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
|
||||
const (
|
||||
offset = 0
|
||||
defaultMTU = 0 /* auto */
|
||||
)
|
||||
|
||||
func init() {
|
||||
guid, _ := windows.GUIDFromString("{330EAEF8-7578-5DF2-D97B-8DADC0EA85CB}")
|
||||
|
||||
tun.WintunTunnelType = "Meta"
|
||||
tun.WintunStaticRequestedGUID = &guid
|
||||
}
|
||||
|
||||
func (t *TUN) LUID() uint64 {
|
||||
return t.nt.LUID()
|
||||
}
|
||||
|
||||
func newDevice(name string, mtu int) (nt tun.Device, err error) {
|
||||
if err = driver.InitWintun(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return tun.CreateTUN(name, mtu)
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
package commons
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/vishvananda/netlink"
|
||||
"go.uber.org/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
monitorStarted = atomic.NewBool(false)
|
||||
monitorStop = make(chan struct{}, 2)
|
||||
)
|
||||
|
||||
func StartDefaultInterfaceChangeMonitor() {
|
||||
go func() {
|
||||
if monitorStarted.Load() {
|
||||
return
|
||||
}
|
||||
monitorStarted.Store(true)
|
||||
|
||||
done := make(chan struct{})
|
||||
ch := make(chan netlink.RouteUpdate, 2)
|
||||
err := netlink.RouteSubscribe(ch, done)
|
||||
if err != nil {
|
||||
log.Warnln("[TUN] auto detect interface fail: %s", err)
|
||||
return
|
||||
}
|
||||
log.Debugln("[TUN] start auto detect interface monitor")
|
||||
defer func() {
|
||||
close(done)
|
||||
monitorStarted.Store(false)
|
||||
log.Debugln("[TUN] stop auto detect interface monitor")
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-monitorStop:
|
||||
default:
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-monitorStop:
|
||||
return
|
||||
case <-ch:
|
||||
}
|
||||
|
||||
interfaceName, err := GetAutoDetectInterface()
|
||||
if err != nil {
|
||||
t := time.NewTicker(2 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case ch <- <-ch:
|
||||
break
|
||||
case <-t.C:
|
||||
interfaceName, err = GetAutoDetectInterface()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
t.Stop()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Debugln("[TUN] detect interface: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
old := dialer.DefaultInterface.Load()
|
||||
if interfaceName == old {
|
||||
continue
|
||||
}
|
||||
|
||||
dialer.DefaultInterface.Store(interfaceName)
|
||||
iface.FlushCache()
|
||||
|
||||
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func StopDefaultInterfaceChangeMonitor() {
|
||||
if monitorStarted.Load() {
|
||||
monitorStop <- struct{}{}
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
//go:build !linux
|
||||
|
||||
package commons
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"go.uber.org/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
monitorDuration = 3 * time.Second
|
||||
monitorStarted = atomic.NewBool(false)
|
||||
monitorStop = make(chan struct{}, 2)
|
||||
)
|
||||
|
||||
func StartDefaultInterfaceChangeMonitor() {
|
||||
go func() {
|
||||
if monitorStarted.Load() {
|
||||
return
|
||||
}
|
||||
monitorStarted.Store(true)
|
||||
t := time.NewTicker(monitorDuration)
|
||||
log.Debugln("[TUN] start auto detect interface monitor")
|
||||
defer func() {
|
||||
monitorStarted.Store(false)
|
||||
t.Stop()
|
||||
log.Debugln("[TUN] stop auto detect interface monitor")
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-monitorStop:
|
||||
default:
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
interfaceName, err := GetAutoDetectInterface()
|
||||
if err != nil {
|
||||
log.Warnln("[TUN] default interface monitor err: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
old := dialer.DefaultInterface.Load()
|
||||
if interfaceName == old {
|
||||
continue
|
||||
}
|
||||
|
||||
dialer.DefaultInterface.Store(interfaceName)
|
||||
iface.FlushCache()
|
||||
|
||||
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName)
|
||||
case <-monitorStop:
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func StopDefaultInterfaceChangeMonitor() {
|
||||
if monitorStarted.Load() {
|
||||
monitorStop <- struct{}{}
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package commons
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
const DefaultDnsReadTimeout = time.Second * 10
|
||||
|
||||
func ShouldHijackDns(dnsAdds []netip.AddrPort, targetAddr netip.AddrPort) bool {
|
||||
for _, addrPort := range dnsAdds {
|
||||
if addrPort == targetAddr || (addrPort.Addr().IsUnspecified() && targetAddr.Port() == 53) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func RelayDnsPacket(payload []byte) ([]byte, error) {
|
||||
msg := &D.Msg{}
|
||||
if err := msg.Unpack(payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, err := resolver.ServeMsg(msg)
|
||||
if err != nil {
|
||||
m := new(D.Msg)
|
||||
m.SetRcode(msg, D.RcodeServerFailure)
|
||||
return m.Pack()
|
||||
}
|
||||
|
||||
r.SetRcode(msg, r.Rcode)
|
||||
r.Compress = true
|
||||
return r.Pack()
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package commons
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultRoutes = []string{"1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1"}
|
||||
)
|
||||
|
||||
func ipv4MaskString(bits int) string {
|
||||
m := net.CIDRMask(bits, 32)
|
||||
if len(m) != 4 {
|
||||
panic("ipv4Mask: len must be 4 bytes")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%d.%d.%d.%d", m[0], m[1], m[2], m[3])
|
||||
}
|
||||
|
||||
func WaitForTunClose(deviceName string) {
|
||||
t := time.NewTicker(600 * time.Millisecond)
|
||||
defer t.Stop()
|
||||
log.Debugln("[TUN] waiting for device close")
|
||||
for {
|
||||
<-t.C
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
found := false
|
||||
for i := len(interfaces) - 1; i > -1; i-- {
|
||||
if interfaces[i].Name == deviceName {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
@ -1,140 +0,0 @@
|
||||
package commons
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/vishvananda/netlink"
|
||||
"net"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
func GetAutoDetectInterface() (ifn string, err error) {
|
||||
routes, err := netlink.RouteGetWithOptions(
|
||||
net.ParseIP("1.1.1.1"),
|
||||
&netlink.RouteGetOptions{
|
||||
Uid: &netlink.UID{Uid: 4294967295},
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
if lk, err := netlink.LinkByIndex(route.LinkIndex); err == nil {
|
||||
return lk.Attrs().Name, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("interface not found")
|
||||
}
|
||||
|
||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
|
||||
var (
|
||||
interfaceName = dev.Name()
|
||||
ip = addr.Masked().Addr().Next()
|
||||
)
|
||||
|
||||
metaLink, err := netlink.LinkByName(interfaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
naddr, err := netlink.ParseAddr(addr.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = netlink.AddrAdd(metaLink, naddr); err != nil && err.Error() != "file exists" {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = netlink.LinkSetUp(metaLink); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if autoRoute {
|
||||
err = configInterfaceRouting(metaLink.Attrs().Index, interfaceName, ip)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func configInterfaceRouting(index int, interfaceName string, ip netip.Addr) error {
|
||||
const tableId = 1981801
|
||||
var pref = 9000
|
||||
|
||||
for _, route := range defaultRoutes {
|
||||
_, ipn, err := net.ParseCIDR(route)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := netlink.RouteAdd(&netlink.Route{
|
||||
LinkIndex: index,
|
||||
Scope: netlink.SCOPE_LINK,
|
||||
Protocol: 2,
|
||||
Src: ip.AsSlice(),
|
||||
Dst: ipn,
|
||||
Table: tableId,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
logIfErr := func(e error) {
|
||||
if e != nil {
|
||||
log.Warnln("[TOUTE] config route rule: %s", e)
|
||||
}
|
||||
}
|
||||
|
||||
var r *netlink.Rule
|
||||
r = netlink.NewRule()
|
||||
r.Table = 254
|
||||
r.Priority = pref
|
||||
logIfErr(netlink.RuleAdd(r))
|
||||
pref += 10
|
||||
|
||||
r = netlink.NewRule()
|
||||
_, nl, _ := net.ParseCIDR("0.0.0.0/32")
|
||||
r.Table = tableId
|
||||
r.Priority = pref
|
||||
r.Src = nl
|
||||
r.IifName = "lo"
|
||||
r.UID = netlink.NewRuleUIDRange(0, 4294967294)
|
||||
logIfErr(netlink.RuleAdd(r))
|
||||
pref += 10
|
||||
|
||||
_, nl, _ = net.ParseCIDR(ip.String())
|
||||
r.Priority = pref
|
||||
r.Src = nl
|
||||
logIfErr(netlink.RuleAdd(r))
|
||||
pref += 10
|
||||
|
||||
r = netlink.NewRule()
|
||||
r.Table = 254
|
||||
r.Priority = pref
|
||||
r.IifName = interfaceName
|
||||
r.SuppressPrefixlen = 0
|
||||
logIfErr(netlink.RuleAdd(r))
|
||||
pref += 10
|
||||
|
||||
r = netlink.NewRule()
|
||||
r.Table = tableId
|
||||
r.Priority = pref
|
||||
r.IifName = "lo"
|
||||
r.SuppressPrefixlen = 0
|
||||
r.Invert = true
|
||||
logIfErr(netlink.RuleAdd(r))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CleanupRule() {
|
||||
r := netlink.NewRule()
|
||||
for i := 0; i < 5; i++ {
|
||||
r.Priority = 9000 + i*10
|
||||
err := netlink.RuleDel(r)
|
||||
if err != nil {
|
||||
log.Warnln("[TOUTE] cleanup route rule: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
package commons
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cmd"
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
)
|
||||
|
||||
func GetAutoDetectInterface() (string, error) {
|
||||
return cmd.ExecCmd("bash -c route -n get default | grep 'interface:' | awk -F ' ' 'NR==1{print $2}' | xargs echo -n")
|
||||
}
|
||||
|
||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
|
||||
if !addr.Addr().Is4() {
|
||||
return fmt.Errorf("supported ipv4 only")
|
||||
}
|
||||
|
||||
var (
|
||||
interfaceName = dev.Name()
|
||||
ip = addr.Masked().Addr().Next()
|
||||
gw = ip.Next()
|
||||
netmask = ipv4MaskString(addr.Bits())
|
||||
)
|
||||
|
||||
cmdStr := fmt.Sprintf("ifconfig %s inet %s netmask %s %s", interfaceName, ip, netmask, gw)
|
||||
|
||||
_, err := cmd.ExecCmd(cmdStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = cmd.ExecCmd(fmt.Sprintf("ipconfig set %s automatic-v6", interfaceName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if autoRoute {
|
||||
err = configInterfaceRouting(interfaceName, addr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func configInterfaceRouting(interfaceName string, addr netip.Prefix) error {
|
||||
var (
|
||||
routes = append(defaultRoutes, addr.String())
|
||||
gateway = addr.Masked().Addr().Next()
|
||||
)
|
||||
|
||||
for _, destination := range routes {
|
||||
if _, err := cmd.ExecCmd(fmt.Sprintf("route add -net %s %s", destination, gateway)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return execRouterCmd("add", "-inet6", "2000::/3", interfaceName)
|
||||
}
|
||||
|
||||
func execRouterCmd(action, inet, route string, interfaceName string) error {
|
||||
_, err := cmd.ExecCmd(fmt.Sprintf("route %s %s %s -interface %s", action, inet, route, interfaceName))
|
||||
return err
|
||||
}
|
||||
|
||||
func CleanupRule() {}
|
@ -1,89 +0,0 @@
|
||||
//go:build !android
|
||||
|
||||
package commons
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
"github.com/vishvananda/netlink"
|
||||
"net"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
func GetAutoDetectInterface() (string, error) {
|
||||
routes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
if route.Dst == nil {
|
||||
lk, err := netlink.LinkByIndex(route.LinkIndex)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if lk.Type() == "tuntap" {
|
||||
continue
|
||||
}
|
||||
|
||||
return lk.Attrs().Name, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("interface not found")
|
||||
}
|
||||
|
||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
|
||||
var (
|
||||
interfaceName = dev.Name()
|
||||
ip = addr.Masked().Addr().Next()
|
||||
)
|
||||
|
||||
metaLink, err := netlink.LinkByName(interfaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nlAddr, err := netlink.ParseAddr(addr.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = netlink.AddrAdd(metaLink, nlAddr); err != nil && err.Error() != "file exists" {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = netlink.LinkSetUp(metaLink); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if autoRoute {
|
||||
_ = configInterfaceRouting(metaLink.Attrs().Index, ip)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func configInterfaceRouting(index int, ip netip.Addr) error {
|
||||
for _, route := range defaultRoutes {
|
||||
_, ipn, err := net.ParseCIDR(route)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := netlink.RouteAdd(&netlink.Route{
|
||||
LinkIndex: index,
|
||||
Scope: netlink.SCOPE_LINK,
|
||||
Protocol: 2,
|
||||
Src: ip.AsSlice(),
|
||||
Dst: ipn,
|
||||
Table: 254,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CleanupRule() {}
|
@ -1,21 +0,0 @@
|
||||
//go:build !darwin && !linux && !windows
|
||||
|
||||
package commons
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
)
|
||||
|
||||
func GetAutoDetectInterface() (string, error) {
|
||||
return "", fmt.Errorf("can not auto detect interface-name on this OS: %s, you must be detecting interface-name by manual", runtime.GOOS)
|
||||
}
|
||||
|
||||
func ConfigInterfaceAddress(device.Device, netip.Prefix, int, bool) error {
|
||||
return fmt.Errorf("unsupported on this OS: %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
func CleanupRule() {}
|
@ -1,273 +0,0 @@
|
||||
package commons
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/nnip"
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
"github.com/Dreamacro/clash/listener/tun/device/tun"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.zx2c4.com/wireguard/windows/services"
|
||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
||||
)
|
||||
|
||||
var wintunInterfaceName string
|
||||
|
||||
func GetAutoDetectInterface() (string, error) {
|
||||
ifname, err := getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(windows.AF_INET))
|
||||
if err == nil {
|
||||
return ifname, err
|
||||
}
|
||||
|
||||
return getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(windows.AF_INET6))
|
||||
}
|
||||
|
||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
|
||||
retryOnFailure := services.StartedAtBoot()
|
||||
tryTimes := 0
|
||||
var err error
|
||||
startOver:
|
||||
if tryTimes > 0 {
|
||||
log.Infoln("[TUN] retrying interface configuration after failure because system just booted (T+%v): %v", windows.DurationSinceBoot(), err)
|
||||
time.Sleep(time.Second)
|
||||
retryOnFailure = retryOnFailure && tryTimes < 15
|
||||
}
|
||||
tryTimes++
|
||||
|
||||
var (
|
||||
luid = winipcfg.LUID(dev.(*tun.TUN).LUID())
|
||||
ip = addr.Masked().Addr().Next()
|
||||
gw = ip.Next()
|
||||
addresses = []netip.Prefix{netip.PrefixFrom(ip, addr.Bits())}
|
||||
dnsAddress = []netip.Addr{gw}
|
||||
|
||||
family4 = winipcfg.AddressFamily(windows.AF_INET)
|
||||
familyV6 = winipcfg.AddressFamily(windows.AF_INET6)
|
||||
currentFamily = winipcfg.AddressFamily(windows.AF_INET6)
|
||||
)
|
||||
|
||||
if addr.Addr().Is4() {
|
||||
currentFamily = winipcfg.AddressFamily(windows.AF_INET)
|
||||
}
|
||||
|
||||
err = luid.FlushRoutes(windows.AF_INET6)
|
||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
||||
goto startOver
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
err = luid.FlushIPAddresses(windows.AF_INET6)
|
||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
||||
goto startOver
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
err = luid.FlushDNS(windows.AF_INET6)
|
||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
||||
goto startOver
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
err = luid.FlushDNS(windows.AF_INET)
|
||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
||||
goto startOver
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
foundDefault4 := false
|
||||
foundDefault6 := false
|
||||
|
||||
if autoRoute {
|
||||
var (
|
||||
allowedIPs []netip.Prefix
|
||||
|
||||
// add default
|
||||
routeArr = []string{"0.0.0.0/0"}
|
||||
)
|
||||
|
||||
for _, route := range routeArr {
|
||||
allowedIPs = append(allowedIPs, netip.MustParsePrefix(route))
|
||||
}
|
||||
|
||||
estimatedRouteCount := len(allowedIPs)
|
||||
routes := make(map[winipcfg.RouteData]bool, estimatedRouteCount)
|
||||
|
||||
for _, allowedip := range allowedIPs {
|
||||
route := winipcfg.RouteData{
|
||||
Destination: allowedip.Masked(),
|
||||
Metric: 0,
|
||||
}
|
||||
if allowedip.Addr().Is4() {
|
||||
if allowedip.Bits() == 0 {
|
||||
foundDefault4 = true
|
||||
}
|
||||
route.NextHop = netip.IPv4Unspecified()
|
||||
} else if allowedip.Addr().Is6() {
|
||||
if allowedip.Bits() == 0 {
|
||||
foundDefault6 = true
|
||||
}
|
||||
route.NextHop = netip.IPv6Unspecified()
|
||||
}
|
||||
routes[route] = true
|
||||
}
|
||||
|
||||
deduplicatedRoutes := make([]*winipcfg.RouteData, 0, len(routes))
|
||||
for route := range routes {
|
||||
r := route
|
||||
deduplicatedRoutes = append(deduplicatedRoutes, &r)
|
||||
}
|
||||
|
||||
// add gateway
|
||||
deduplicatedRoutes = append(deduplicatedRoutes, &winipcfg.RouteData{
|
||||
Destination: addr.Masked(),
|
||||
NextHop: gw,
|
||||
Metric: 0,
|
||||
})
|
||||
|
||||
err = luid.SetRoutesForFamily(currentFamily, deduplicatedRoutes)
|
||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
||||
goto startOver
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("unable to set routes: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = luid.SetIPAddressesForFamily(currentFamily, addresses)
|
||||
if err == windows.ERROR_OBJECT_ALREADY_EXISTS {
|
||||
cleanupAddressesOnDisconnectedInterfaces(currentFamily, addresses)
|
||||
err = luid.SetIPAddressesForFamily(currentFamily, addresses)
|
||||
}
|
||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
||||
goto startOver
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("unable to set ips: %w", err)
|
||||
}
|
||||
|
||||
var ipif *winipcfg.MibIPInterfaceRow
|
||||
ipif, err = luid.IPInterface(family4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ipif.ForwardingEnabled = true
|
||||
ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
|
||||
ipif.DadTransmits = 0
|
||||
ipif.ManagedAddressConfigurationSupported = false
|
||||
ipif.OtherStatefulConfigurationSupported = false
|
||||
if forceMTU > 0 {
|
||||
ipif.NLMTU = uint32(forceMTU)
|
||||
}
|
||||
if foundDefault4 {
|
||||
ipif.UseAutomaticMetric = false
|
||||
ipif.Metric = 0
|
||||
}
|
||||
err = ipif.Set()
|
||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
||||
goto startOver
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("unable to set metric and MTU: %w", err)
|
||||
}
|
||||
|
||||
var ipif6 *winipcfg.MibIPInterfaceRow
|
||||
ipif6, err = luid.IPInterface(familyV6)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ipif6.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
|
||||
ipif6.DadTransmits = 0
|
||||
ipif6.ManagedAddressConfigurationSupported = false
|
||||
ipif6.OtherStatefulConfigurationSupported = false
|
||||
if forceMTU > 0 {
|
||||
ipif6.NLMTU = uint32(forceMTU)
|
||||
}
|
||||
if foundDefault6 {
|
||||
ipif6.UseAutomaticMetric = false
|
||||
ipif6.Metric = 0
|
||||
}
|
||||
err = ipif6.Set()
|
||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
||||
goto startOver
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("unable to set v6 metric and MTU: %w", err)
|
||||
}
|
||||
|
||||
err = luid.SetDNS(family4, dnsAddress, nil)
|
||||
if err == windows.ERROR_NOT_FOUND && retryOnFailure {
|
||||
goto startOver
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("unable to set DNS %s %s: %w", dnsAddress[0].String(), "nil", err)
|
||||
}
|
||||
|
||||
wintunInterfaceName = dev.Name()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []netip.Prefix) {
|
||||
if len(addresses) == 0 {
|
||||
return
|
||||
}
|
||||
addrHash := make(map[netip.Addr]bool, len(addresses))
|
||||
for i := range addresses {
|
||||
addrHash[addresses[i].Addr()] = true
|
||||
}
|
||||
interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, iface := range interfaces {
|
||||
if iface.OperStatus == winipcfg.IfOperStatusUp {
|
||||
continue
|
||||
}
|
||||
for address := iface.FirstUnicastAddress; address != nil; address = address.Next {
|
||||
if ip := nnip.IpToAddr(address.Address.IP()); addrHash[ip] {
|
||||
prefix := netip.PrefixFrom(ip, int(address.OnLinkPrefixLength))
|
||||
log.Infoln("[TUN] cleaning up stale address %s from interface ‘%s’", prefix.String(), iface.FriendlyName())
|
||||
_ = iface.LUID.DeleteIPAddress(prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getAutoDetectInterfaceByFamily(family winipcfg.AddressFamily) (string, error) {
|
||||
interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagIncludeGateways)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get ethernet interface failure. %w", err)
|
||||
}
|
||||
|
||||
var destination netip.Prefix
|
||||
if family == windows.AF_INET {
|
||||
destination = netip.PrefixFrom(netip.IPv4Unspecified(), 0)
|
||||
} else {
|
||||
destination = netip.PrefixFrom(netip.IPv6Unspecified(), 0)
|
||||
}
|
||||
|
||||
for _, iface := range interfaces {
|
||||
if iface.OperStatus != winipcfg.IfOperStatusUp {
|
||||
continue
|
||||
}
|
||||
|
||||
ifname := iface.FriendlyName()
|
||||
|
||||
if wintunInterfaceName == ifname {
|
||||
continue
|
||||
}
|
||||
|
||||
for gatewayAddress := iface.FirstGatewayAddress; gatewayAddress != nil; gatewayAddress = gatewayAddress.Next {
|
||||
nextHop := nnip.IpToAddr(gatewayAddress.Address.IP())
|
||||
|
||||
if _, err = iface.LUID.Route(destination, nextHop); err == nil {
|
||||
return ifname, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("ethernet interface not found")
|
||||
}
|
||||
|
||||
func CleanupRule() {}
|
@ -1,26 +0,0 @@
|
||||
//go:build !no_gvisor
|
||||
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
// TCPConn implements the net.Conn interface.
|
||||
type TCPConn interface {
|
||||
net.Conn
|
||||
|
||||
// ID returns the transport endpoint id of TCPConn.
|
||||
ID() *stack.TransportEndpointID
|
||||
}
|
||||
|
||||
// UDPConn implements net.Conn and net.PacketConn.
|
||||
type UDPConn interface {
|
||||
net.Conn
|
||||
net.PacketConn
|
||||
|
||||
// ID returns the transport endpoint id of UDPConn.
|
||||
ID() *stack.TransportEndpointID
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
//go:build !no_gvisor
|
||||
|
||||
package adapter
|
||||
|
||||
// Handler is a TCP/UDP connection handler that implements
|
||||
// HandleTCPConn and HandleUDPConn methods.
|
||||
type Handler interface {
|
||||
HandleTCP(TCPConn)
|
||||
HandleUDP(UDPConn)
|
||||
}
|
||||
|
||||
// TCPHandleFunc handles incoming TCP connection.
|
||||
type TCPHandleFunc func(TCPConn)
|
||||
|
||||
// UDPHandleFunc handles incoming UDP connection.
|
||||
type UDPHandleFunc func(UDPConn)
|
@ -1,142 +0,0 @@
|
||||
//go:build !no_gvisor
|
||||
|
||||
package gvisor
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/common/nnip"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
D "github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/adapter"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
var _ adapter.Handler = (*gvHandler)(nil)
|
||||
|
||||
type gvHandler struct {
|
||||
gateway netip.Addr
|
||||
dnsHijack []netip.AddrPort
|
||||
|
||||
tcpIn chan<- C.ConnContext
|
||||
udpIn chan<- *inbound.PacketAdapter
|
||||
}
|
||||
|
||||
func (gh *gvHandler) HandleTCP(tunConn adapter.TCPConn) {
|
||||
id := tunConn.ID()
|
||||
|
||||
rAddr := &net.TCPAddr{
|
||||
IP: net.IP(id.LocalAddress),
|
||||
Port: int(id.LocalPort),
|
||||
Zone: "",
|
||||
}
|
||||
|
||||
rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), id.LocalPort)
|
||||
|
||||
if D.ShouldHijackDns(gh.dnsHijack, rAddrPort) {
|
||||
go func() {
|
||||
buf := pool.Get(pool.UDPBufferSize)
|
||||
defer func() {
|
||||
_ = pool.Put(buf)
|
||||
_ = tunConn.Close()
|
||||
}()
|
||||
|
||||
for {
|
||||
if tunConn.SetReadDeadline(time.Now().Add(D.DefaultDnsReadTimeout)) != nil {
|
||||
break
|
||||
}
|
||||
|
||||
length := uint16(0)
|
||||
if err := binary.Read(tunConn, binary.BigEndian, &length); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if int(length) > len(buf) {
|
||||
break
|
||||
}
|
||||
|
||||
n, err := tunConn.Read(buf[:length])
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
msg, err := D.RelayDnsPacket(buf[:n])
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
_, _ = tunConn.Write(msg)
|
||||
}
|
||||
}()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
gh.tcpIn <- inbound.NewSocket(socks5.ParseAddrToSocksAddr(rAddr), tunConn, C.TUN)
|
||||
}
|
||||
|
||||
func (gh *gvHandler) HandleUDP(tunConn adapter.UDPConn) {
|
||||
id := tunConn.ID()
|
||||
|
||||
rAddr := &net.UDPAddr{
|
||||
IP: net.IP(id.LocalAddress),
|
||||
Port: int(id.LocalPort),
|
||||
Zone: "",
|
||||
}
|
||||
|
||||
rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), id.LocalPort)
|
||||
|
||||
if rAddrPort.Addr() == gh.gateway {
|
||||
_ = tunConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
target := socks5.ParseAddrToSocksAddr(rAddr)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
buf := pool.Get(pool.UDPBufferSize)
|
||||
|
||||
n, addr, err := tunConn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
_ = pool.Put(buf)
|
||||
break
|
||||
}
|
||||
|
||||
payload := buf[:n]
|
||||
|
||||
if D.ShouldHijackDns(gh.dnsHijack, rAddrPort) {
|
||||
go func() {
|
||||
defer func() {
|
||||
_ = pool.Put(buf)
|
||||
}()
|
||||
|
||||
msg, err1 := D.RelayDnsPacket(payload)
|
||||
if err1 != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = tunConn.WriteTo(msg, addr)
|
||||
}()
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
gvPacket := &packet{
|
||||
pc: tunConn,
|
||||
rAddr: addr,
|
||||
payload: payload,
|
||||
}
|
||||
|
||||
select {
|
||||
case gh.udpIn <- inbound.NewPacket(target, gvPacket, C.TUN):
|
||||
default:
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
//go:build !no_gvisor
|
||||
|
||||
package gvisor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/option"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
const (
|
||||
// nicPromiscuousModeEnabled is the value used by stack to enable
|
||||
// or disable NIC's promiscuous mode.
|
||||
nicPromiscuousModeEnabled = true
|
||||
|
||||
// nicSpoofingEnabled is the value used by stack to enable or disable
|
||||
// NIC's spoofing.
|
||||
nicSpoofingEnabled = true
|
||||
)
|
||||
|
||||
// withCreatingNIC creates NIC for stack.
|
||||
func withCreatingNIC(nicID tcpip.NICID, ep stack.LinkEndpoint) option.Option {
|
||||
return func(s *stack.Stack) error {
|
||||
if err := s.CreateNICWithOptions(nicID, ep,
|
||||
stack.NICOptions{
|
||||
Disabled: false,
|
||||
// If no queueing discipline was specified
|
||||
// provide a stub implementation that just
|
||||
// delegates to the lower link endpoint.
|
||||
QDisc: nil,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("create NIC: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// withPromiscuousMode sets promiscuous mode in the given NICs.
|
||||
func withPromiscuousMode(nicID tcpip.NICID, v bool) option.Option {
|
||||
return func(s *stack.Stack) error {
|
||||
if err := s.SetPromiscuousMode(nicID, v); err != nil {
|
||||
return fmt.Errorf("set promiscuous mode: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// withSpoofing sets address spoofing in the given NICs, allowing
|
||||
// endpoints to bind to any address in the NIC.
|
||||
func withSpoofing(nicID tcpip.NICID, v bool) option.Option {
|
||||
return func(s *stack.Stack) error {
|
||||
if err := s.SetSpoofing(nicID, v); err != nil {
|
||||
return fmt.Errorf("set spoofing: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
@ -1,260 +0,0 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultTimeToLive specifies the default TTL used by stack.
|
||||
defaultTimeToLive uint8 = 64
|
||||
|
||||
// ipForwardingEnabled is the value used by stack to enable packet
|
||||
// forwarding between NICs.
|
||||
ipForwardingEnabled = true
|
||||
|
||||
// icmpBurst is the default number of ICMP messages that can be sent in
|
||||
// a single burst.
|
||||
icmpBurst = 50
|
||||
|
||||
// icmpLimit is the default maximum number of ICMP messages permitted
|
||||
// by this rate limiter.
|
||||
icmpLimit rate.Limit = 1000
|
||||
|
||||
// tcpCongestionControl is the congestion control algorithm used by
|
||||
// stack. ccReno is the default option in gVisor stack.
|
||||
tcpCongestionControlAlgorithm = "reno" // "reno" or "cubic"
|
||||
|
||||
// tcpDelayEnabled is the value used by stack to enable or disable
|
||||
// tcp delay option. Disable Nagle's algorithm here by default.
|
||||
tcpDelayEnabled = false
|
||||
|
||||
// tcpModerateReceiveBufferEnabled is the value used by stack to
|
||||
// enable or disable tcp receive buffer auto-tuning option.
|
||||
tcpModerateReceiveBufferEnabled = false
|
||||
|
||||
// tcpSACKEnabled is the value used by stack to enable or disable
|
||||
// tcp selective ACK.
|
||||
tcpSACKEnabled = true
|
||||
|
||||
// tcpRecovery is the loss detection algorithm used by TCP.
|
||||
tcpRecovery = tcpip.TCPRACKLossDetection
|
||||
|
||||
// tcpMinBufferSize is the smallest size of a send/recv buffer.
|
||||
tcpMinBufferSize = tcp.MinBufferSize
|
||||
|
||||
// tcpMaxBufferSize is the maximum permitted size of a send/recv buffer.
|
||||
tcpMaxBufferSize = pool.RelayBufferSize
|
||||
|
||||
// tcpDefaultBufferSize is the default size of the send buffer for
|
||||
// a transport endpoint.
|
||||
tcpDefaultSendBufferSize = pool.RelayBufferSize
|
||||
|
||||
// tcpDefaultReceiveBufferSize is the default size of the receive buffer
|
||||
// for a transport endpoint.
|
||||
tcpDefaultReceiveBufferSize = pool.RelayBufferSize
|
||||
)
|
||||
|
||||
type Option func(*stack.Stack) error
|
||||
|
||||
// WithDefault sets all default values for stack.
|
||||
func WithDefault() Option {
|
||||
return func(s *stack.Stack) error {
|
||||
opts := []Option{
|
||||
WithDefaultTTL(defaultTimeToLive),
|
||||
WithForwarding(ipForwardingEnabled),
|
||||
|
||||
// Config default stack ICMP settings.
|
||||
WithICMPBurst(icmpBurst), WithICMPLimit(icmpLimit),
|
||||
|
||||
// We expect no packet loss, therefore we can bump buffers.
|
||||
// Too large buffers thrash cache, so there is little point
|
||||
// in too large buffers.
|
||||
//
|
||||
// Ref: https://github.com/cloudflare/slirpnetstack/blob/master/stack.go
|
||||
WithTCPSendBufferSizeRange(tcpMinBufferSize, tcpDefaultSendBufferSize, tcpMaxBufferSize),
|
||||
WithTCPReceiveBufferSizeRange(tcpMinBufferSize, tcpDefaultReceiveBufferSize, tcpMaxBufferSize),
|
||||
|
||||
WithTCPCongestionControl(tcpCongestionControlAlgorithm),
|
||||
WithTCPDelay(tcpDelayEnabled),
|
||||
|
||||
// Receive Buffer Auto-Tuning Option, see:
|
||||
// https://github.com/google/gvisor/issues/1666
|
||||
WithTCPModerateReceiveBuffer(tcpModerateReceiveBufferEnabled),
|
||||
|
||||
// TCP selective ACK Option, see:
|
||||
// https://tools.ietf.org/html/rfc2018
|
||||
WithTCPSACKEnabled(tcpSACKEnabled),
|
||||
|
||||
// TCPRACKLossDetection: indicates RACK is used for loss detection and
|
||||
// recovery.
|
||||
//
|
||||
// TCPRACKStaticReoWnd: indicates the reordering window should not be
|
||||
// adjusted when DSACK is received.
|
||||
//
|
||||
// TCPRACKNoDupTh: indicates RACK should not consider the classic three
|
||||
// duplicate acknowledgements rule to mark the segments as lost. This
|
||||
// is used when reordering is not detected.
|
||||
WithTCPRecovery(tcpRecovery),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithDefaultTTL sets the default TTL used by stack.
|
||||
func WithDefaultTTL(ttl uint8) Option {
|
||||
return func(s *stack.Stack) error {
|
||||
opt := tcpip.DefaultTTLOption(ttl)
|
||||
if err := s.SetNetworkProtocolOption(ipv4.ProtocolNumber, &opt); err != nil {
|
||||
return fmt.Errorf("set ipv4 default TTL: %s", err)
|
||||
}
|
||||
if err := s.SetNetworkProtocolOption(ipv6.ProtocolNumber, &opt); err != nil {
|
||||
return fmt.Errorf("set ipv6 default TTL: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithForwarding sets packet forwarding between NICs for IPv4 & IPv6.
|
||||
func WithForwarding(v bool) Option {
|
||||
return func(s *stack.Stack) error {
|
||||
if err := s.SetForwardingDefaultAndAllNICs(ipv4.ProtocolNumber, v); err != nil {
|
||||
return fmt.Errorf("set ipv4 forwarding: %s", err)
|
||||
}
|
||||
if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, v); err != nil {
|
||||
return fmt.Errorf("set ipv6 forwarding: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithICMPBurst sets the number of ICMP messages that can be sent
|
||||
// in a single burst.
|
||||
func WithICMPBurst(burst int) Option {
|
||||
return func(s *stack.Stack) error {
|
||||
s.SetICMPBurst(burst)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithICMPLimit sets the maximum number of ICMP messages permitted
|
||||
// by rate limiter.
|
||||
func WithICMPLimit(limit rate.Limit) Option {
|
||||
return func(s *stack.Stack) error {
|
||||
s.SetICMPLimit(limit)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTCPSendBufferSize sets default the send buffer size for TCP.
|
||||
func WithTCPSendBufferSize(size int) Option {
|
||||
return func(s *stack.Stack) error {
|
||||
sndOpt := tcpip.TCPSendBufferSizeRangeOption{Min: tcpMinBufferSize, Default: size, Max: tcpMaxBufferSize}
|
||||
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &sndOpt); err != nil {
|
||||
return fmt.Errorf("set TCP send buffer size range: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTCPSendBufferSizeRange sets the send buffer size range for TCP.
|
||||
func WithTCPSendBufferSizeRange(a, b, c int) Option {
|
||||
return func(s *stack.Stack) error {
|
||||
sndOpt := tcpip.TCPSendBufferSizeRangeOption{Min: a, Default: b, Max: c}
|
||||
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &sndOpt); err != nil {
|
||||
return fmt.Errorf("set TCP send buffer size range: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTCPReceiveBufferSize sets the default receive buffer size for TCP.
|
||||
func WithTCPReceiveBufferSize(size int) Option {
|
||||
return func(s *stack.Stack) error {
|
||||
rcvOpt := tcpip.TCPReceiveBufferSizeRangeOption{Min: tcpMinBufferSize, Default: size, Max: tcpMaxBufferSize}
|
||||
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &rcvOpt); err != nil {
|
||||
return fmt.Errorf("set TCP receive buffer size range: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTCPReceiveBufferSizeRange sets the receive buffer size range for TCP.
|
||||
func WithTCPReceiveBufferSizeRange(a, b, c int) Option {
|
||||
return func(s *stack.Stack) error {
|
||||
rcvOpt := tcpip.TCPReceiveBufferSizeRangeOption{Min: a, Default: b, Max: c}
|
||||
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &rcvOpt); err != nil {
|
||||
return fmt.Errorf("set TCP receive buffer size range: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTCPCongestionControl sets the current congestion control algorithm.
|
||||
func WithTCPCongestionControl(cc string) Option {
|
||||
return func(s *stack.Stack) error {
|
||||
opt := tcpip.CongestionControlOption(cc)
|
||||
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
|
||||
return fmt.Errorf("set TCP congestion control algorithm: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTCPDelay enables or disables Nagle's algorithm in TCP.
|
||||
func WithTCPDelay(v bool) Option {
|
||||
return func(s *stack.Stack) error {
|
||||
opt := tcpip.TCPDelayEnabled(v)
|
||||
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
|
||||
return fmt.Errorf("set TCP delay: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTCPModerateReceiveBuffer sets receive buffer moderation for TCP.
|
||||
func WithTCPModerateReceiveBuffer(v bool) Option {
|
||||
return func(s *stack.Stack) error {
|
||||
opt := tcpip.TCPModerateReceiveBufferOption(v)
|
||||
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
|
||||
return fmt.Errorf("set TCP moderate receive buffer: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTCPSACKEnabled sets the SACK option for TCP.
|
||||
func WithTCPSACKEnabled(v bool) Option {
|
||||
return func(s *stack.Stack) error {
|
||||
opt := tcpip.TCPSACKEnabled(v)
|
||||
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
|
||||
return fmt.Errorf("set TCP SACK: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTCPRecovery sets the recovery option for TCP.
|
||||
func WithTCPRecovery(v tcpip.TCPRecovery) Option {
|
||||
return func(s *stack.Stack) error {
|
||||
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &v); err != nil {
|
||||
return fmt.Errorf("set TCP Recovery: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
//go:build !no_gvisor
|
||||
|
||||
package gvisor
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/option"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
func withRouteTable(nicID tcpip.NICID) option.Option {
|
||||
return func(s *stack.Stack) error {
|
||||
s.SetRouteTable([]tcpip.Route{
|
||||
{
|
||||
Destination: header.IPv4EmptySubnet,
|
||||
NIC: nicID,
|
||||
},
|
||||
{
|
||||
Destination: header.IPv6EmptySubnet,
|
||||
NIC: nicID,
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
//go:build !no_gvisor
|
||||
|
||||
// Package gvisor provides a thin wrapper around a gVisor's stack.
|
||||
package gvisor
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/option"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
||||
)
|
||||
|
||||
type gvStack struct {
|
||||
*stack.Stack
|
||||
device device.Device
|
||||
}
|
||||
|
||||
func (s *gvStack) Close() error {
|
||||
var err error
|
||||
if s.device != nil {
|
||||
err = s.device.Close()
|
||||
}
|
||||
if s.Stack != nil {
|
||||
s.Stack.Close()
|
||||
s.Stack.Wait()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// New allocates a new *gvStack with given options.
|
||||
func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter, opts ...option.Option) (ipstack.Stack, error) {
|
||||
s := &gvStack{
|
||||
Stack: stack.New(stack.Options{
|
||||
NetworkProtocols: []stack.NetworkProtocolFactory{
|
||||
ipv4.NewProtocol,
|
||||
ipv6.NewProtocol,
|
||||
},
|
||||
TransportProtocols: []stack.TransportProtocolFactory{
|
||||
tcp.NewProtocol,
|
||||
udp.NewProtocol,
|
||||
icmp.NewProtocol4,
|
||||
icmp.NewProtocol6,
|
||||
},
|
||||
}),
|
||||
|
||||
device: device,
|
||||
}
|
||||
|
||||
handler := &gvHandler{
|
||||
gateway: tunAddress.Masked().Addr().Next(),
|
||||
dnsHijack: dnsHijack,
|
||||
tcpIn: tcpIn,
|
||||
udpIn: udpIn,
|
||||
}
|
||||
|
||||
// Generate unique NIC id.
|
||||
nicID := tcpip.NICID(s.Stack.UniqueID())
|
||||
defaultOpts := []option.Option{option.WithDefault()}
|
||||
defaultOpts = append(defaultOpts, opts...)
|
||||
opts = append(defaultOpts,
|
||||
// Create stack NIC and then bind link endpoint to it.
|
||||
withCreatingNIC(nicID, device),
|
||||
|
||||
// In the past we did s.AddAddressRange to assign 0.0.0.0/0
|
||||
// onto the interface. We need that to be able to terminate
|
||||
// all the incoming connections - to any ip. AddressRange API
|
||||
// has been removed and the suggested workaround is to use
|
||||
// Promiscuous mode. https://github.com/google/gvisor/issues/3876
|
||||
//
|
||||
// Ref: https://github.com/cloudflare/slirpnetstack/blob/master/stack.go
|
||||
withPromiscuousMode(nicID, nicPromiscuousModeEnabled),
|
||||
|
||||
// Enable spoofing if a stack may send packets from unowned
|
||||
// addresses. This change required changes to some netgophers
|
||||
// since previously, promiscuous mode was enough to let the
|
||||
// netstack respond to all incoming packets regardless of the
|
||||
// packet's destination address. Now that a stack.Route is not
|
||||
// held for each incoming packet, finding a route may fail with
|
||||
// local addresses we don't own but accepted packets for while
|
||||
// in promiscuous mode. Since we also want to be able to send
|
||||
// from any address (in response the received promiscuous mode
|
||||
// packets), we need to enable spoofing.
|
||||
//
|
||||
// Ref: https://github.com/google/gvisor/commit/8c0701462a84ff77e602f1626aec49479c308127
|
||||
withSpoofing(nicID, nicSpoofingEnabled),
|
||||
|
||||
// Add default route table for IPv4 and IPv6. This will handle
|
||||
// all incoming ICMP packets.
|
||||
withRouteTable(nicID),
|
||||
|
||||
// Initiate transport protocol (TCP/UDP) with given handler.
|
||||
withTCPHandler(handler.HandleTCP), withUDPHandler(handler.HandleUDP),
|
||||
)
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(s.Stack); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return s, nil
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
//go:build no_gvisor
|
||||
|
||||
package gvisor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
// New allocates a new *gvStack with given options.
|
||||
func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.Stack, error) {
|
||||
log.Fatalln("unsupported gvisor stack on the build")
|
||||
return nil, fmt.Errorf("unsupported gvisor stack on the build")
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user