Compare commits

..

29 Commits

Author SHA1 Message Date
wwqgtxx
5b975275f5 fix: incorrect checking of strings.Split return value
strings.Split will never return a slice of length 0 if sep is not empty, so any code that checks if the return value is of length 0 is incorrect and useless.
2025-06-25 16:20:37 +08:00
ayanamist
166392fe17 chore: sniffer replace domain only if domain is valid (#2122) 2025-06-24 21:44:26 +08:00
ayanamist
5c6aa433ca chore: unconditionally allow clients with passwords for password-free socks5 inbound (#2123) 2025-06-24 19:01:12 +08:00
xishang0128
2c55dc2557 action: fix run build on pull_request 2025-06-24 19:01:03 +08:00
wwqgtxx
56c0b088e8 doc: update path doc 2025-06-21 22:46:55 +08:00
Restia-Ashbell
5344e869a8 fix: ssr uri decode (#2116) 2025-06-21 12:19:13 +08:00
wwqgtxx
6cfaf15cbf fix: missing error return 2025-06-21 12:08:41 +08:00
wwqgtxx
31f0060b30 fix: chacha20 counter overflow
the implement it's a not safe chacha20 using but for compatible
2025-06-21 10:42:14 +08:00
wwqgtxx
c60750d549 chore: allow tun to skip the system ipv6 check when starting by environment variable SKIP_SYSTEM_IPV6_CHECK 2025-06-14 15:57:54 +08:00
wwqgtxx
ebf5918e94 fix: v2ray-plugin mux maybe not close underlay connection 2025-06-14 12:32:45 +08:00
riolurs
93ca18517c chore: converter support fingerprint for anytls 2025-06-13 23:05:06 +08:00
beck
32d447ce99 fix: convert https (#2102) 2025-06-12 17:10:09 +08:00
beck
617fef84ae feat: converter support anytls/socks/http (#2100) 2025-06-12 16:17:25 +08:00
wwqgtxx
d19199322d action: don't trigger cmfa update on pull request 2025-06-12 15:33:19 +08:00
wwqgtxx
87795e3a07 chore: add yaml marshal for common/atomic 2025-06-12 15:24:29 +08:00
wwqgtxx
85bb40aaf8 chore: add Int32Enum for common/atomic 2025-06-12 15:24:29 +08:00
wwqgtxx
082bcec281 chore: apply find process mode in direct/global mode 2025-06-12 00:27:51 +08:00
wwqgtxx
9283cb0f5f feat: add loopback-address support for tun 2025-06-11 17:45:28 +08:00
wwqgtxx
ae7967f662 chore: the resolve and findProcess behaviors of Logic and SubRules follow the order and needs of the internal rules 2025-06-10 20:11:50 +08:00
wwqgtxx
01f8f2db2f chore: cleanup allocator code 2025-06-10 10:54:08 +08:00
wwqgtxx
255ff5e977 chore: add rate limiting support for reality listener 2025-06-10 10:40:26 +08:00
wwqgtxx
939e4109d7 chore: write dns reply in single syscall 2025-06-07 00:38:39 +08:00
wwqgtxx
40587b62b8 feat: all dns client support skip-cert-verify params 2025-06-06 00:52:12 +08:00
wwqgtxx
85e6d25de5 feat: all dns client support ecs and ecs-override params 2025-06-06 00:45:58 +08:00
wwqgtxx
29a37f4f4b feat: all dns client support disable-ipv4 and disable-ipv6 params 2025-06-06 00:24:57 +08:00
wwqgtxx
2f9a3b3469 chore: cleanup code 2025-06-05 21:20:38 +08:00
wwqgtxx
40ea0ba098 fix: correct constructor for 2022-blake3-chacha8-poly1305 2025-06-05 13:47:26 +08:00
wwqgtxx
8d7f947a80 fix: TypedValue.CompareAndSwap
84aa7ff3bb
2025-06-05 13:43:30 +08:00
wwqgtxx
71a8705636 fix: remote dst parse 2025-05-31 22:57:05 +08:00
76 changed files with 1004 additions and 876 deletions

View File

@@ -198,10 +198,11 @@ jobs:
- name: Set variables - name: Set variables
run: | run: |
VERSION="${GITHUB_REF_NAME,,}-$(git rev-parse --short HEAD)" VERSION="${GITHUB_REF_NAME,,}-$(git rev-parse --short HEAD)"
VERSION="${VERSION//\//-}"
PackageVersion="$(curl -s "https://api.github.com/repos/MetaCubeX/mihomo/releases/latest" | jq -r '.tag_name' | sed 's/v//g' | awk -F '.' '{$NF = $NF + 1; print}' OFS='.').${VERSION/-/.}" PackageVersion="$(curl -s "https://api.github.com/repos/MetaCubeX/mihomo/releases/latest" | jq -r '.tag_name' | sed 's/v//g' | awk -F '.' '{$NF = $NF + 1; print}' OFS='.').${VERSION/-/.}"
if [ -n "${{ github.event.inputs.version }}" ]; then if [ -n "${{ github.event.inputs.version }}" ]; then
VERSION=${{ github.event.inputs.version }} VERSION=${{ github.event.inputs.version }}
PackageVersion="${VERSION#v}" >> $GITHUB_ENV PackageVersion="${VERSION#v}"
fi fi
echo "VERSION=${VERSION}" >> $GITHUB_ENV echo "VERSION=${VERSION}" >> $GITHUB_ENV
echo "PackageVersion=${PackageVersion}" >> $GITHUB_ENV echo "PackageVersion=${PackageVersion}" >> $GITHUB_ENV

View File

@@ -10,9 +10,6 @@ on:
- Alpha - Alpha
tags: tags:
- "v*" - "v*"
pull_request_target:
branches:
- Alpha
jobs: jobs:
# Send "core-updated" to MetaCubeX/ClashMetaForAndroid to trigger update-dependencies # Send "core-updated" to MetaCubeX/ClashMetaForAndroid to trigger update-dependencies

View File

@@ -223,7 +223,7 @@ type conn struct {
func (c *conn) RemoteDestination() string { func (c *conn) RemoteDestination() string {
if remoteAddr := c.RemoteAddr(); remoteAddr != nil { if remoteAddr := c.RemoteAddr(); remoteAddr != nil {
m := C.Metadata{} m := C.Metadata{}
if err := m.SetRemoteAddr(remoteAddr); err != nil { if err := m.SetRemoteAddr(remoteAddr); err == nil {
if m.Valid() { if m.Valid() {
return m.String() return m.String()
} }

63
common/atomic/enum.go Normal file
View File

@@ -0,0 +1,63 @@
package atomic
import (
"encoding/json"
"fmt"
"sync/atomic"
)
type Int32Enum[T ~int32] struct {
value atomic.Int32
}
func (i *Int32Enum[T]) MarshalJSON() ([]byte, error) {
return json.Marshal(i.Load())
}
func (i *Int32Enum[T]) UnmarshalJSON(b []byte) error {
var v T
if err := json.Unmarshal(b, &v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Int32Enum[T]) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Int32Enum[T]) UnmarshalYAML(unmarshal func(any) error) error {
var v T
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Int32Enum[T]) String() string {
return fmt.Sprint(i.Load())
}
func (i *Int32Enum[T]) Store(v T) {
i.value.Store(int32(v))
}
func (i *Int32Enum[T]) Load() T {
return T(i.value.Load())
}
func (i *Int32Enum[T]) Swap(new T) T {
return T(i.value.Swap(int32(new)))
}
func (i *Int32Enum[T]) CompareAndSwap(old, new T) bool {
return i.value.CompareAndSwap(int32(old), int32(new))
}
func NewInt32Enum[T ~int32](v T) *Int32Enum[T] {
a := &Int32Enum[T]{}
a.Store(v)
return a
}

View File

@@ -29,6 +29,19 @@ func (i *Bool) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func (i *Bool) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Bool) UnmarshalYAML(unmarshal func(any) error) error {
var v bool
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Bool) String() string { func (i *Bool) String() string {
v := i.Load() v := i.Load()
return strconv.FormatBool(v) return strconv.FormatBool(v)
@@ -58,6 +71,19 @@ func (p *Pointer[T]) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func (p *Pointer[T]) MarshalYAML() (any, error) {
return p.Load(), nil
}
func (p *Pointer[T]) UnmarshalYAML(unmarshal func(any) error) error {
var v *T
if err := unmarshal(&v); err != nil {
return err
}
p.Store(v)
return nil
}
func (p *Pointer[T]) String() string { func (p *Pointer[T]) String() string {
return fmt.Sprint(p.Load()) return fmt.Sprint(p.Load())
} }
@@ -84,6 +110,19 @@ func (i *Int32) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func (i *Int32) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Int32) UnmarshalYAML(unmarshal func(any) error) error {
var v int32
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Int32) String() string { func (i *Int32) String() string {
v := i.Load() v := i.Load()
return strconv.FormatInt(int64(v), 10) return strconv.FormatInt(int64(v), 10)
@@ -111,6 +150,19 @@ func (i *Int64) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func (i *Int64) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Int64) UnmarshalYAML(unmarshal func(any) error) error {
var v int64
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Int64) String() string { func (i *Int64) String() string {
v := i.Load() v := i.Load()
return strconv.FormatInt(int64(v), 10) return strconv.FormatInt(int64(v), 10)
@@ -138,6 +190,19 @@ func (i *Uint32) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func (i *Uint32) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Uint32) UnmarshalYAML(unmarshal func(any) error) error {
var v uint32
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Uint32) String() string { func (i *Uint32) String() string {
v := i.Load() v := i.Load()
return strconv.FormatUint(uint64(v), 10) return strconv.FormatUint(uint64(v), 10)
@@ -165,6 +230,19 @@ func (i *Uint64) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func (i *Uint64) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Uint64) UnmarshalYAML(unmarshal func(any) error) error {
var v uint64
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Uint64) String() string { func (i *Uint64) String() string {
v := i.Load() v := i.Load()
return strconv.FormatUint(uint64(v), 10) return strconv.FormatUint(uint64(v), 10)
@@ -192,6 +270,19 @@ func (i *Uintptr) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func (i *Uintptr) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Uintptr) UnmarshalYAML(unmarshal func(any) error) error {
var v uintptr
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Uintptr) String() string { func (i *Uintptr) String() string {
v := i.Load() v := i.Load()
return strconv.FormatUint(uint64(v), 10) return strconv.FormatUint(uint64(v), 10)

View File

@@ -27,11 +27,16 @@ type tValue[T any] struct {
} }
func (t *TypedValue[T]) Load() T { func (t *TypedValue[T]) Load() T {
value, _ := t.LoadOk()
return value
}
func (t *TypedValue[T]) LoadOk() (_ T, ok bool) {
value := t.value.Load() value := t.value.Load()
if value == nil { if value == nil {
return DefaultValue[T]() return DefaultValue[T](), false
} }
return value.(tValue[T]).value return value.(tValue[T]).value, true
} }
func (t *TypedValue[T]) Store(value T) { func (t *TypedValue[T]) Store(value T) {
@@ -47,7 +52,11 @@ func (t *TypedValue[T]) Swap(new T) T {
} }
func (t *TypedValue[T]) CompareAndSwap(old, new T) bool { func (t *TypedValue[T]) CompareAndSwap(old, new T) bool {
return t.value.CompareAndSwap(tValue[T]{old}, tValue[T]{new}) return t.value.CompareAndSwap(tValue[T]{old}, tValue[T]{new}) ||
// In the edge-case where [atomic.Value.Store] is uninitialized
// and trying to compare with the zero value of T,
// then compare-and-swap with the nil any value.
(any(old) == any(DefaultValue[T]()) && t.value.CompareAndSwap(any(nil), tValue[T]{new}))
} }
func (t *TypedValue[T]) MarshalJSON() ([]byte, error) { func (t *TypedValue[T]) MarshalJSON() ([]byte, error) {
@@ -63,6 +72,19 @@ func (t *TypedValue[T]) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func (t *TypedValue[T]) MarshalYAML() (any, error) {
return t.Load(), nil
}
func (t *TypedValue[T]) UnmarshalYAML(unmarshal func(any) error) error {
var v T
if err := unmarshal(&v); err != nil {
return err
}
t.Store(v)
return nil
}
func NewTypedValue[T any](t T) (v TypedValue[T]) { func NewTypedValue[T any](t T) (v TypedValue[T]) {
v.Store(t) v.Store(t)
return return

View File

@@ -0,0 +1,77 @@
package atomic
import (
"io"
"os"
"testing"
)
func TestTypedValue(t *testing.T) {
{
// Always wrapping should not allocate for simple values
// because tValue[T] has the same memory layout as T.
var v TypedValue[bool]
bools := []bool{true, false}
if n := int(testing.AllocsPerRun(1000, func() {
for _, b := range bools {
v.Store(b)
}
})); n != 0 {
t.Errorf("AllocsPerRun = %d, want 0", n)
}
}
{
var v TypedValue[int]
got, gotOk := v.LoadOk()
if got != 0 || gotOk {
t.Fatalf("LoadOk = (%v, %v), want (0, false)", got, gotOk)
}
v.Store(1)
got, gotOk = v.LoadOk()
if got != 1 || !gotOk {
t.Fatalf("LoadOk = (%v, %v), want (1, true)", got, gotOk)
}
}
{
var v TypedValue[error]
got, gotOk := v.LoadOk()
if got != nil || gotOk {
t.Fatalf("LoadOk = (%v, %v), want (nil, false)", got, gotOk)
}
v.Store(io.EOF)
got, gotOk = v.LoadOk()
if got != io.EOF || !gotOk {
t.Fatalf("LoadOk = (%v, %v), want (EOF, true)", got, gotOk)
}
err := &os.PathError{}
v.Store(err)
got, gotOk = v.LoadOk()
if got != err || !gotOk {
t.Fatalf("LoadOk = (%v, %v), want (%v, true)", got, gotOk, err)
}
v.Store(nil)
got, gotOk = v.LoadOk()
if got != nil || !gotOk {
t.Fatalf("LoadOk = (%v, %v), want (nil, true)", got, gotOk)
}
}
{
c1, c2, c3 := make(chan struct{}), make(chan struct{}), make(chan struct{})
var v TypedValue[chan struct{}]
if v.CompareAndSwap(c1, c2) != false {
t.Fatalf("CompareAndSwap = true, want false")
}
if v.CompareAndSwap(nil, c1) != true {
t.Fatalf("CompareAndSwap = false, want true")
}
if v.CompareAndSwap(c2, c3) != false {
t.Fatalf("CompareAndSwap = true, want false")
}
if v.CompareAndSwap(c1, c2) != true {
t.Fatalf("CompareAndSwap = false, want true")
}
}
}

View File

@@ -2,6 +2,7 @@ package convert
import ( import (
"encoding/base64" "encoding/base64"
"fmt"
"strings" "strings"
) )
@@ -43,3 +44,22 @@ func decodeUrlSafe(data string) string {
} }
return string(dcBuf) return string(dcBuf)
} }
func TryDecodeBase64(s string) (decoded []byte, err error) {
if len(s)%4 == 0 {
if decoded, err = base64.StdEncoding.DecodeString(s); err == nil {
return
}
if decoded, err = base64.URLEncoding.DecodeString(s); err == nil {
return
}
} else {
if decoded, err = base64.RawStdEncoding.DecodeString(s); err == nil {
return
}
if decoded, err = base64.RawURLEncoding.DecodeString(s); err == nil {
return
}
}
return nil, fmt.Errorf("invalid base64-encoded string")
}

View File

@@ -456,12 +456,12 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
proxies = append(proxies, ss) proxies = append(proxies, ss)
case "ssr": case "ssr":
dcBuf, err := encRaw.DecodeString(body) dcBuf, err := TryDecodeBase64(body)
if err != nil { if err != nil {
continue continue
} }
// ssr://host:port:protocol:method:obfs:urlsafebase64pass/?obfsparam=urlsafebase64&protoparam=&remarks=urlsafebase64&group=urlsafebase64&udpport=0&uot=1 // ssr://host:port:protocol:method:obfs:urlsafebase64pass/?obfsparam=urlsafebase64param&protoparam=urlsafebase64param&remarks=urlsafebase64remarks&group=urlsafebase64group&udpport=0&uot=1
before, after, ok := strings.Cut(string(dcBuf), "/?") before, after, ok := strings.Cut(string(dcBuf), "/?")
if !ok { if !ok {
@@ -490,7 +490,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
name := uniqueName(names, remarks) name := uniqueName(names, remarks)
obfsParam := decodeUrlSafe(query.Get("obfsparam")) obfsParam := decodeUrlSafe(query.Get("obfsparam"))
protocolParam := query.Get("protoparam") protocolParam := decodeUrlSafe(query.Get("protoparam"))
ssr := make(map[string]any, 20) ssr := make(map[string]any, 20)
@@ -513,6 +513,101 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
} }
proxies = append(proxies, ssr) proxies = append(proxies, ssr)
case "socks", "socks5", "socks5h", "http", "https":
link, err := url.Parse(line)
if err != nil {
continue
}
server := link.Hostname()
if server == "" {
continue
}
portStr := link.Port()
if portStr == "" {
continue
}
remarks := link.Fragment
if remarks == "" {
remarks = fmt.Sprintf("%s:%s", server, portStr)
}
name := uniqueName(names, remarks)
encodeStr := link.User.String()
var username, password string
if encodeStr != "" {
decodeStr := string(DecodeBase64([]byte(encodeStr)))
splitStr := strings.Split(decodeStr, ":")
// todo: should use url.QueryUnescape ?
username = splitStr[0]
if len(splitStr) == 2 {
password = splitStr[1]
}
}
socks := make(map[string]any, 10)
socks["name"] = name
socks["type"] = func() string {
switch scheme {
case "socks", "socks5", "socks5h":
return "socks5"
case "http", "https":
return "http"
}
return scheme
}()
socks["server"] = server
socks["port"] = portStr
socks["username"] = username
socks["password"] = password
socks["skip-cert-verify"] = true
if scheme == "https" {
socks["tls"] = true
}
proxies = append(proxies, socks)
case "anytls":
// https://github.com/anytls/anytls-go/blob/main/docs/uri_scheme.md
link, err := url.Parse(line)
if err != nil {
continue
}
username := link.User.Username()
password, exist := link.User.Password()
if !exist {
password = username
}
query := link.Query()
server := link.Hostname()
if server == "" {
continue
}
portStr := link.Port()
if portStr == "" {
continue
}
insecure, sni := query.Get("insecure"), query.Get("sni")
insecureBool := insecure == "1"
fingerprint := query.Get("hpkp")
remarks := link.Fragment
if remarks == "" {
remarks = fmt.Sprintf("%s:%s", server, portStr)
}
name := uniqueName(names, remarks)
anytls := make(map[string]any, 10)
anytls["name"] = name
anytls["type"] = "anytls"
anytls["server"] = server
anytls["port"] = portStr
anytls["username"] = username
anytls["password"] = password
anytls["sni"] = sni
anytls["fingerprint"] = fingerprint
anytls["skip-cert-verify"] = insecureBool
anytls["udp"] = true
proxies = append(proxies, anytls)
} }
} }

View File

@@ -8,18 +8,23 @@ import (
"sync" "sync"
) )
var defaultAllocator = NewAllocator() var DefaultAllocator = NewAllocator()
// Allocator for incoming frames, optimized to prevent overwriting after zeroing type Allocator interface {
type Allocator struct { Get(size int) []byte
Put(buf []byte) error
}
// defaultAllocator for incoming frames, optimized to prevent overwriting after zeroing
type defaultAllocator struct {
buffers [11]sync.Pool buffers [11]sync.Pool
} }
// NewAllocator initiates a []byte allocator for frames less than 65536 bytes, // NewAllocator initiates a []byte allocator for frames less than 65536 bytes,
// the waste(memory fragmentation) of space allocation is guaranteed to be // the waste(memory fragmentation) of space allocation is guaranteed to be
// no more than 50%. // no more than 50%.
func NewAllocator() *Allocator { func NewAllocator() Allocator {
return &Allocator{ return &defaultAllocator{
buffers: [...]sync.Pool{ // 64B -> 64K buffers: [...]sync.Pool{ // 64B -> 64K
{New: func() any { return new([1 << 6]byte) }}, {New: func() any { return new([1 << 6]byte) }},
{New: func() any { return new([1 << 7]byte) }}, {New: func() any { return new([1 << 7]byte) }},
@@ -37,7 +42,7 @@ func NewAllocator() *Allocator {
} }
// Get a []byte from pool with most appropriate cap // Get a []byte from pool with most appropriate cap
func (alloc *Allocator) Get(size int) []byte { func (alloc *defaultAllocator) Get(size int) []byte {
switch { switch {
case size < 0: case size < 0:
panic("alloc.Get: len out of range") panic("alloc.Get: len out of range")
@@ -87,7 +92,7 @@ func (alloc *Allocator) Get(size int) []byte {
// Put returns a []byte to pool for future use, // Put returns a []byte to pool for future use,
// which the cap must be exactly 2^n // which the cap must be exactly 2^n
func (alloc *Allocator) Put(buf []byte) error { func (alloc *defaultAllocator) Put(buf []byte) error {
if cap(buf) == 0 || cap(buf) > 65536 { if cap(buf) == 0 || cap(buf) > 65536 {
return nil return nil
} }

View File

@@ -3,13 +3,12 @@
package pool package pool
const ( const (
// RelayBufferSize using for tcp
// io.Copy default buffer size is 32 KiB // io.Copy default buffer size is 32 KiB
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
RelayBufferSize = 16 * 1024 RelayBufferSize = 16 * 1024
// RelayBufferSize uses 20KiB, but due to the allocator it will actually // UDPBufferSize using for udp
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU // Most UDPs are smaller than the MTU, and the TUN's MTU
// set to 9000, so the UDP Buffer size set to 16Kib // set to 9000, so the UDP Buffer size set to 16Kib
UDPBufferSize = 8 * 1024 UDPBufferSize = 8 * 1024
) )

View File

@@ -3,13 +3,12 @@
package pool package pool
const ( const (
// RelayBufferSize using for tcp
// io.Copy default buffer size is 32 KiB // io.Copy default buffer size is 32 KiB
// but the maximum packet size of vmess/shadowsocks is about 16 KiB RelayBufferSize = 32 * 1024
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
RelayBufferSize = 20 * 1024
// RelayBufferSize uses 20KiB, but due to the allocator it will actually // UDPBufferSize using for udp
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU // Most UDPs are smaller than the MTU, and the TUN's MTU
// set to 9000, so the UDP Buffer size set to 16Kib // set to 9000, so the UDP Buffer size set to 16Kib
UDPBufferSize = 16 * 1024 UDPBufferSize = 16 * 1024
) )

View File

@@ -1,9 +1,9 @@
package pool package pool
func Get(size int) []byte { func Get(size int) []byte {
return defaultAllocator.Get(size) return DefaultAllocator.Get(size)
} }
func Put(buf []byte) error { func Put(buf []byte) error {
return defaultAllocator.Put(buf) return DefaultAllocator.Put(buf)
} }

View File

@@ -3,5 +3,5 @@ package pool
import "github.com/metacubex/sing/common/buf" import "github.com/metacubex/sing/common/buf"
func init() { func init() {
buf.DefaultAllocator = defaultAllocator buf.DefaultAllocator = DefaultAllocator
} }

View File

@@ -239,13 +239,15 @@ func (n *num) UnmarshalText(text []byte) (err error) {
func TestStructure_TextUnmarshaller(t *testing.T) { func TestStructure_TextUnmarshaller(t *testing.T) {
rawMap := map[string]any{ rawMap := map[string]any{
"num": "255", "num": "255",
"num_p": "127", "num_p": "127",
"num_arr": []string{"1", "2", "3"},
} }
s := &struct { s := &struct {
Num num `test:"num"` Num num `test:"num"`
NumP *num `test:"num_p"` NumP *num `test:"num_p"`
NumArr []num `test:"num_arr"`
}{} }{}
err := decoder.Decode(rawMap, s) err := decoder.Decode(rawMap, s)
@@ -253,6 +255,7 @@ func TestStructure_TextUnmarshaller(t *testing.T) {
assert.Equal(t, 255, s.Num.a) assert.Equal(t, 255, s.Num.a)
assert.NotNil(t, s.NumP) assert.NotNil(t, s.NumP)
assert.Equal(t, s.NumP.a, 127) assert.Equal(t, s.NumP.a, 127)
assert.Equal(t, s.NumArr, []num{{1}, {2}, {3}})
// test WeaklyTypedInput // test WeaklyTypedInput
rawMap["num"] = 256 rawMap["num"] = 256

View File

@@ -41,3 +41,11 @@ func NewAuthenticator(users []AuthUser) Authenticator {
} }
return au return au
} }
var AlwaysValid Authenticator = alwaysValid{}
type alwaysValid struct{}
func (alwaysValid) Verify(string, string) bool { return true }
func (alwaysValid) Users() []string { return nil }

View File

@@ -1,7 +1,6 @@
package geodata package geodata
import ( import (
"errors"
"fmt" "fmt"
"strings" "strings"
@@ -76,13 +75,13 @@ func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, error) {
if countryCode[0] == '!' { if countryCode[0] == '!' {
not = true not = true
countryCode = countryCode[1:] countryCode = countryCode[1:]
if countryCode == "" {
return nil, fmt.Errorf("country code could not be empty")
}
} }
countryCode = strings.ToLower(countryCode) countryCode = strings.ToLower(countryCode)
parts := strings.Split(countryCode, "@") parts := strings.Split(countryCode, "@")
if len(parts) == 0 {
return nil, errors.New("empty rule")
}
listName := strings.TrimSpace(parts[0]) listName := strings.TrimSpace(parts[0])
attrVal := parts[1:] attrVal := parts[1:]
attrs := parseAttrs(attrVal) attrs := parseAttrs(attrVal)

View File

@@ -1,57 +1,52 @@
package process package process
import ( import (
"encoding/json"
"errors" "errors"
"strings" "strings"
) )
const ( const (
FindProcessAlways = "always" FindProcessStrict FindProcessMode = iota
FindProcessStrict = "strict" FindProcessAlways
FindProcessOff = "off" FindProcessOff
) )
var ( var (
validModes = map[string]struct{}{ validModes = map[string]FindProcessMode{
FindProcessAlways: {}, FindProcessStrict.String(): FindProcessStrict,
FindProcessOff: {}, FindProcessAlways.String(): FindProcessAlways,
FindProcessStrict: {}, FindProcessOff.String(): FindProcessOff,
} }
) )
type FindProcessMode string type FindProcessMode int32
func (m FindProcessMode) Always() bool { // UnmarshalText unserialize FindProcessMode
return m == FindProcessAlways func (m *FindProcessMode) UnmarshalText(data []byte) error {
} return m.Set(string(data))
func (m FindProcessMode) Off() bool {
return m == FindProcessOff
}
func (m *FindProcessMode) UnmarshalYAML(unmarshal func(any) error) error {
var tp string
if err := unmarshal(&tp); err != nil {
return err
}
return m.Set(tp)
}
func (m *FindProcessMode) UnmarshalJSON(data []byte) error {
var tp string
if err := json.Unmarshal(data, &tp); err != nil {
return err
}
return m.Set(tp)
} }
func (m *FindProcessMode) Set(value string) error { func (m *FindProcessMode) Set(value string) error {
mode := strings.ToLower(value) mode, exist := validModes[strings.ToLower(value)]
_, exist := validModes[mode]
if !exist { if !exist {
return errors.New("invalid find process mode") return errors.New("invalid find process mode")
} }
*m = FindProcessMode(mode) *m = mode
return nil return nil
} }
// MarshalText serialize FindProcessMode
func (m FindProcessMode) MarshalText() ([]byte, error) {
return []byte(m.String()), nil
}
func (m FindProcessMode) String() string {
switch m {
case FindProcessAlways:
return "always"
case FindProcessOff:
return "off"
default:
return "strict"
}
}

View File

@@ -46,17 +46,24 @@ func RelayDnsConn(ctx context.Context, conn net.Conn, readTimeout time.Duration)
ctx, cancel := context.WithTimeout(ctx, DefaultDnsRelayTimeout) ctx, cancel := context.WithTimeout(ctx, DefaultDnsRelayTimeout)
defer cancel() defer cancel()
inData := buff[:n] inData := buff[:n]
msg, err := relayDnsPacket(ctx, inData, buff, 0) outBuff := buff[2:]
msg, err := relayDnsPacket(ctx, inData, outBuff, 0)
if err != nil { if err != nil {
return err return err
} }
err = binary.Write(conn, binary.BigEndian, uint16(len(msg))) if &msg[0] == &outBuff[0] { // msg is still in the buff
if err != nil { binary.BigEndian.PutUint16(buff[:2], uint16(len(msg)))
return err outBuff = buff[:2+len(msg)]
} else { // buff not big enough (WTF???)
newBuff := pool.Get(len(msg) + 2)
defer pool.Put(newBuff)
binary.BigEndian.PutUint16(newBuff[:2], uint16(len(msg)))
copy(newBuff[2:], msg)
outBuff = newBuff
} }
_, err = conn.Write(msg) _, err = conn.Write(outBuff)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -6,6 +6,8 @@ import (
"net/netip" "net/netip"
"time" "time"
"github.com/metacubex/sing/common/metadata"
"github.com/metacubex/mihomo/common/lru" "github.com/metacubex/mihomo/common/lru"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
@@ -164,6 +166,9 @@ func replaceDomain(metadata *C.Metadata, host string, overrideDest bool) {
} }
func (sd *Dispatcher) domainCanReplace(host string) bool { func (sd *Dispatcher) domainCanReplace(host string) bool {
if host == "." || !metadata.IsDomainName(host) {
return false
}
for _, matcher := range sd.skipDomain { for _, matcher := range sd.skipDomain {
if matcher.MatchDomain(host) { if matcher.MatchDomain(host) {
return false return false

View File

@@ -270,6 +270,7 @@ type RawTun struct {
AutoRedirect bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"` AutoRedirect bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"`
AutoRedirectInputMark uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"` AutoRedirectInputMark uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"`
AutoRedirectOutputMark uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"` AutoRedirectOutputMark uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"`
LoopbackAddress []netip.Addr `yaml:"loopback-address" json:"loopback-address,omitempty"`
StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"` StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"`
RouteAddress []netip.Prefix `yaml:"route-address" json:"route-address,omitempty"` RouteAddress []netip.Prefix `yaml:"route-address" json:"route-address,omitempty"`
RouteAddressSet []string `yaml:"route-address-set" json:"route-address-set,omitempty"` RouteAddressSet []string `yaml:"route-address-set" json:"route-address-set,omitempty"`
@@ -1168,10 +1169,19 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns.
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
} }
proxyName := u.Fragment var proxyName string
params := map[string]string{}
for _, s := range strings.Split(u.Fragment, "&") {
arr := strings.SplitN(s, "=", 2)
switch len(arr) {
case 1:
proxyName = arr[0]
case 2:
params[arr[0]] = arr[1]
}
}
var addr, dnsNetType string var addr, dnsNetType string
params := map[string]string{}
switch u.Scheme { switch u.Scheme {
case "udp": case "udp":
addr, err = hostWithDefaultPort(u.Host, "53") addr, err = hostWithDefaultPort(u.Host, "53")
@@ -1189,23 +1199,8 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns.
addr, err = hostWithDefaultPort(u.Host, "80") addr, err = hostWithDefaultPort(u.Host, "80")
} }
if err == nil { if err == nil {
proxyName = ""
clearURL := url.URL{Scheme: u.Scheme, Host: addr, Path: u.Path, User: u.User} clearURL := url.URL{Scheme: u.Scheme, Host: addr, Path: u.Path, User: u.User}
addr = clearURL.String() addr = clearURL.String()
if len(u.Fragment) != 0 {
for _, s := range strings.Split(u.Fragment, "&") {
arr := strings.Split(s, "=")
if len(arr) == 0 {
continue
} else if len(arr) == 1 {
proxyName = arr[0]
} else if len(arr) == 2 {
params[arr[0]] = arr[1]
} else {
params[arr[0]] = strings.Join(arr[1:], "=")
}
}
}
} }
case "quic": case "quic":
addr, err = hostWithDefaultPort(u.Host, "853") addr, err = hostWithDefaultPort(u.Host, "853")
@@ -1563,6 +1558,7 @@ func parseTun(rawTun RawTun, general *General) error {
AutoRedirect: rawTun.AutoRedirect, AutoRedirect: rawTun.AutoRedirect,
AutoRedirectInputMark: rawTun.AutoRedirectInputMark, AutoRedirectInputMark: rawTun.AutoRedirectInputMark,
AutoRedirectOutputMark: rawTun.AutoRedirectOutputMark, AutoRedirectOutputMark: rawTun.AutoRedirectOutputMark,
LoopbackAddress: rawTun.LoopbackAddress,
StrictRoute: rawTun.StrictRoute, StrictRoute: rawTun.StrictRoute,
RouteAddress: rawTun.RouteAddress, RouteAddress: rawTun.RouteAddress,
RouteAddressSet: rawTun.RouteAddressSet, RouteAddressSet: rawTun.RouteAddressSet,

View File

@@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"os"
"strconv"
"strings" "strings"
"github.com/metacubex/mihomo/adapter/outboundgroup" "github.com/metacubex/mihomo/adapter/outboundgroup"
@@ -150,6 +152,9 @@ func proxyGroupsDagSort(groupsConfig []map[string]any) error {
} }
func verifyIP6() bool { func verifyIP6() bool {
if skip, _ := strconv.ParseBool(os.Getenv("SKIP_SYSTEM_IPV6_CHECK")); skip {
return true
}
if iAddrs, err := net.InterfaceAddrs(); err == nil { if iAddrs, err := net.InterfaceAddrs(); err == nil {
for _, addr := range iAddrs { for _, addr := range iAddrs {
if prefix, err := netip.ParsePrefix(addr.String()); err == nil { if prefix, err := netip.ParsePrefix(addr.String()); err == nil {

View File

@@ -1,7 +1,6 @@
package constant package constant
import ( import (
"encoding/json"
"errors" "errors"
"strings" "strings"
) )
@@ -22,44 +21,6 @@ const (
type DNSMode int type DNSMode int
// UnmarshalYAML unserialize EnhancedMode with yaml
func (e *DNSMode) UnmarshalYAML(unmarshal func(any) error) error {
var tp string
if err := unmarshal(&tp); err != nil {
return err
}
mode, exist := DNSModeMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid mode")
}
*e = mode
return nil
}
// MarshalYAML serialize EnhancedMode with yaml
func (e DNSMode) MarshalYAML() (any, error) {
return e.String(), nil
}
// UnmarshalJSON unserialize EnhancedMode with json
func (e *DNSMode) UnmarshalJSON(data []byte) error {
var tp string
if err := json.Unmarshal(data, &tp); err != nil {
return err
}
mode, exist := DNSModeMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid mode")
}
*e = mode
return nil
}
// MarshalJSON serialize EnhancedMode with json
func (e DNSMode) MarshalJSON() ([]byte, error) {
return json.Marshal(e.String())
}
// UnmarshalText unserialize EnhancedMode // UnmarshalText unserialize EnhancedMode
func (e *DNSMode) UnmarshalText(data []byte) error { func (e *DNSMode) UnmarshalText(data []byte) error {
mode, exist := DNSModeMapping[strings.ToLower(string(data))] mode, exist := DNSModeMapping[strings.ToLower(string(data))]
@@ -157,40 +118,6 @@ func (e FilterMode) String() string {
} }
} }
func (e FilterMode) MarshalYAML() (interface{}, error) {
return e.String(), nil
}
func (e *FilterMode) UnmarshalYAML(unmarshal func(interface{}) error) error {
var tp string
if err := unmarshal(&tp); err != nil {
return err
}
mode, exist := FilterModeMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid mode")
}
*e = mode
return nil
}
func (e FilterMode) MarshalJSON() ([]byte, error) {
return json.Marshal(e.String())
}
func (e *FilterMode) UnmarshalJSON(data []byte) error {
var tp string
if err := json.Unmarshal(data, &tp); err != nil {
return err
}
mode, exist := FilterModeMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid mode")
}
*e = mode
return nil
}
func (e FilterMode) MarshalText() ([]byte, error) { func (e FilterMode) MarshalText() ([]byte, error) {
return []byte(e.String()), nil return []byte(e.String()), nil
} }

View File

@@ -91,9 +91,7 @@ type RuleProvider interface {
Provider Provider
Behavior() RuleBehavior Behavior() RuleBehavior
Count() int Count() int
Match(*constant.Metadata) bool Match(metadata *constant.Metadata, helper constant.RuleMatchHelper) bool
ShouldResolveIP() bool
ShouldFindProcess() bool
Strategy() any Strategy() any
} }

View File

@@ -111,14 +111,17 @@ func (rt RuleType) String() string {
type Rule interface { type Rule interface {
RuleType() RuleType RuleType() RuleType
Match(metadata *Metadata) (bool, string) Match(metadata *Metadata, helper RuleMatchHelper) (bool, string)
Adapter() string Adapter() string
Payload() string Payload() string
ShouldResolveIP() bool
ShouldFindProcess() bool
ProviderNames() []string ProviderNames() []string
} }
type RuleMatchHelper struct {
ResolveIP func()
FindProcess func()
}
type RuleGroup interface { type RuleGroup interface {
Rule Rule
GetRecodeSize() int GetRecodeSize() int

View File

@@ -1,7 +1,6 @@
package constant package constant
import ( import (
"encoding/json"
"errors" "errors"
"strings" "strings"
) )
@@ -20,42 +19,6 @@ const (
type TUNStack int type TUNStack int
// UnmarshalYAML unserialize TUNStack with yaml
func (e *TUNStack) UnmarshalYAML(unmarshal func(any) error) error {
var tp string
if err := unmarshal(&tp); err != nil {
return err
}
mode, exist := StackTypeMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid tun stack")
}
*e = mode
return nil
}
// MarshalYAML serialize TUNStack with yaml
func (e TUNStack) MarshalYAML() (any, error) {
return e.String(), nil
}
// UnmarshalJSON unserialize TUNStack with json
func (e *TUNStack) UnmarshalJSON(data []byte) error {
var tp string
json.Unmarshal(data, &tp)
mode, exist := StackTypeMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid tun stack")
}
*e = mode
return nil
}
// MarshalJSON serialize TUNStack with json
func (e TUNStack) MarshalJSON() ([]byte, error) {
return json.Marshal(e.String())
}
// UnmarshalText unserialize TUNStack // UnmarshalText unserialize TUNStack
func (e *TUNStack) UnmarshalText(data []byte) error { func (e *TUNStack) UnmarshalText(data []byte) error {
mode, exist := StackTypeMapping[strings.ToLower(string(data))] mode, exist := StackTypeMapping[strings.ToLower(string(data))]

View File

@@ -6,8 +6,10 @@ import (
"fmt" "fmt"
"net" "net"
"strings" "strings"
"time"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
D "github.com/miekg/dns" D "github.com/miekg/dns"
@@ -105,3 +107,24 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
} }
func (c *client) ResetConnection() {} func (c *client) ResetConnection() {}
func newClient(addr string, resolver *Resolver, netType string, params map[string]string, proxyAdapter C.ProxyAdapter, proxyName string) *client {
host, port, _ := net.SplitHostPort(addr)
c := &client{
Client: &D.Client{
Net: netType,
TLSConfig: &tls.Config{
ServerName: host,
},
UDPSize: 4096,
Timeout: 5 * time.Second,
},
port: port,
host: host,
dialer: newDNSDialer(resolver, proxyAdapter, proxyName),
}
if params["skip-cert-verify"] == "true" {
c.TLSConfig.InsecureSkipVerify = true
}
return c
}

View File

@@ -9,7 +9,6 @@ import (
"io" "io"
"net" "net"
"net/http" "net/http"
"net/netip"
"net/url" "net/url"
"runtime" "runtime"
"strconv" "strconv"
@@ -71,8 +70,6 @@ type dnsOverHTTPS struct {
dialer *dnsDialer dialer *dnsDialer
addr string addr string
skipCertVerify bool skipCertVerify bool
ecsPrefix netip.Prefix
ecsOverride bool
} }
// type check // type check
@@ -105,28 +102,6 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
doh.skipCertVerify = true doh.skipCertVerify = true
} }
if ecs := params["ecs"]; ecs != "" {
prefix, err := netip.ParsePrefix(ecs)
if err != nil {
addr, err := netip.ParseAddr(ecs)
if err != nil {
log.Warnln("DOH [%s] config with invalid ecs: %s", doh.addr, ecs)
} else {
doh.ecsPrefix = netip.PrefixFrom(addr, addr.BitLen())
}
} else {
doh.ecsPrefix = prefix
}
}
if doh.ecsPrefix.IsValid() {
log.Debugln("DOH [%s] config with ecs: %s", doh.addr, doh.ecsPrefix)
}
if params["ecs-override"] == "true" {
doh.ecsOverride = true
}
runtime.SetFinalizer(doh, (*dnsOverHTTPS).Close) runtime.SetFinalizer(doh, (*dnsOverHTTPS).Close)
return doh return doh
@@ -154,10 +129,6 @@ func (doh *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.
} }
}() }()
if doh.ecsPrefix.IsValid() {
setEdns0Subnet(m, doh.ecsPrefix, doh.ecsOverride)
}
// Check if there was already an active client before sending the request. // Check if there was already an active client before sending the request.
// We'll only attempt to re-connect if there was one. // We'll only attempt to re-connect if there was one.
client, isCached, err := doh.getClient(ctx) client, isCached, err := doh.getClient(ctx)
@@ -552,8 +523,8 @@ func (doh *dnsOverHTTPS) createTransportH3(
Dial: func( Dial: func(
ctx context.Context, ctx context.Context,
// Ignore the address and always connect to the one that we got // Ignore the address and always connect to the one that we got
// from the bootstrapper. // from the bootstrapper.
_ string, _ string,
tlsCfg *tlsC.Config, tlsCfg *tlsC.Config,
cfg *quic.Config, cfg *quic.Config,

View File

@@ -61,15 +61,16 @@ type dnsOverQUIC struct {
bytesPool *sync.Pool bytesPool *sync.Pool
bytesPoolGuard sync.Mutex bytesPoolGuard sync.Mutex
addr string addr string
dialer *dnsDialer dialer *dnsDialer
skipCertVerify bool
} }
// type check // type check
var _ dnsClient = (*dnsOverQUIC)(nil) var _ dnsClient = (*dnsOverQUIC)(nil)
// newDoQ returns the DNS-over-QUIC Upstream. // newDoQ returns the DNS-over-QUIC Upstream.
func newDoQ(resolver *Resolver, addr string, proxyAdapter C.ProxyAdapter, proxyName string) (dnsClient, error) { func newDoQ(addr string, resolver *Resolver, params map[string]string, proxyAdapter C.ProxyAdapter, proxyName string) *dnsOverQUIC {
doq := &dnsOverQUIC{ doq := &dnsOverQUIC{
addr: addr, addr: addr,
dialer: newDNSDialer(resolver, proxyAdapter, proxyName), dialer: newDNSDialer(resolver, proxyAdapter, proxyName),
@@ -79,8 +80,12 @@ func newDoQ(resolver *Resolver, addr string, proxyAdapter C.ProxyAdapter, proxyN
}, },
} }
if params["skip-cert-verify"] == "true" {
doq.skipCertVerify = true
}
runtime.SetFinalizer(doq, (*dnsOverQUIC).Close) runtime.SetFinalizer(doq, (*dnsOverQUIC).Close)
return doq, nil return doq
} }
// Address implements the Upstream interface for *dnsOverQUIC. // Address implements the Upstream interface for *dnsOverQUIC.
@@ -329,7 +334,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio
tlsConfig := ca.GetGlobalTLSConfig( tlsConfig := ca.GetGlobalTLSConfig(
&tls.Config{ &tls.Config{
ServerName: host, ServerName: host,
InsecureSkipVerify: false, InsecureSkipVerify: doq.skipCertVerify,
NextProtos: []string{ NextProtos: []string{
NextProtoDQ, NextProtoDQ,
}, },

View File

@@ -2,10 +2,8 @@ package dns
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"net"
"net/netip" "net/netip"
"strings" "strings"
"time" "time"
@@ -92,46 +90,95 @@ func isIPRequest(q D.Question) bool {
func transform(servers []NameServer, resolver *Resolver) []dnsClient { func transform(servers []NameServer, resolver *Resolver) []dnsClient {
ret := make([]dnsClient, 0, len(servers)) ret := make([]dnsClient, 0, len(servers))
for _, s := range servers { for _, s := range servers {
var c dnsClient
switch s.Net { switch s.Net {
case "https": case "https":
ret = append(ret, newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter, s.ProxyName)) c = newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter, s.ProxyName)
continue
case "dhcp": case "dhcp":
ret = append(ret, newDHCPClient(s.Addr)) c = newDHCPClient(s.Addr)
continue
case "system": case "system":
ret = append(ret, newSystemClient()) c = newSystemClient()
continue
case "rcode": case "rcode":
ret = append(ret, newRCodeClient(s.Addr)) c = newRCodeClient(s.Addr)
continue
case "quic": case "quic":
if doq, err := newDoQ(resolver, s.Addr, s.ProxyAdapter, s.ProxyName); err == nil { c = newDoQ(s.Addr, resolver, s.Params, s.ProxyAdapter, s.ProxyName)
ret = append(ret, doq) default:
} else { c = newClient(s.Addr, resolver, s.Net, s.Params, s.ProxyAdapter, s.ProxyName)
log.Fatalln("DoQ format error: %v", err)
}
continue
} }
host, port, _ := net.SplitHostPort(s.Addr) c = warpClientWithEdns0Subnet(c, s.Params)
ret = append(ret, &client{
Client: &D.Client{ if s.Params["disable-ipv4"] == "true" {
Net: s.Net, c = warpClientWithDisableType(c, D.TypeA)
TLSConfig: &tls.Config{ }
ServerName: host,
}, if s.Params["disable-ipv6"] == "true" {
UDPSize: 4096, c = warpClientWithDisableType(c, D.TypeAAAA)
Timeout: 5 * time.Second, }
},
port: port, ret = append(ret, c)
host: host,
dialer: newDNSDialer(resolver, s.ProxyAdapter, s.ProxyName),
})
} }
return ret return ret
} }
type clientWithDisableType struct {
dnsClient
qType uint16
}
func (c clientWithDisableType) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
if len(m.Question) > 0 {
q := m.Question[0]
if q.Qtype == c.qType {
return handleMsgWithEmptyAnswer(m), nil
}
}
return c.dnsClient.ExchangeContext(ctx, m)
}
func warpClientWithDisableType(c dnsClient, qType uint16) dnsClient {
return clientWithDisableType{c, qType}
}
type clientWithEdns0Subnet struct {
dnsClient
ecsPrefix netip.Prefix
ecsOverride bool
}
func (c clientWithEdns0Subnet) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
m = m.Copy()
setEdns0Subnet(m, c.ecsPrefix, c.ecsOverride)
return c.dnsClient.ExchangeContext(ctx, m)
}
func warpClientWithEdns0Subnet(c dnsClient, params map[string]string) dnsClient {
var ecsPrefix netip.Prefix
var ecsOverride bool
if ecs := params["ecs"]; ecs != "" {
prefix, err := netip.ParsePrefix(ecs)
if err != nil {
addr, err := netip.ParseAddr(ecs)
if err != nil {
log.Warnln("DNS [%s] config with invalid ecs: %s", c.Address(), ecs)
} else {
ecsPrefix = netip.PrefixFrom(addr, addr.BitLen())
}
} else {
ecsPrefix = prefix
}
}
if ecsPrefix.IsValid() {
log.Debugln("DNS [%s] config with ecs: %s", c.Address(), ecsPrefix)
if params["ecs-override"] == "true" {
ecsOverride = true
}
return clientWithEdns0Subnet{c, ecsPrefix, ecsOverride}
}
return c
}
func handleMsgWithEmptyAnswer(r *D.Msg) *D.Msg { func handleMsgWithEmptyAnswer(r *D.Msg) *D.Msg {
msg := &D.Msg{} msg := &D.Msg{}
msg.Answer = []D.RR{} msg.Answer = []D.RR{}

View File

@@ -1020,7 +1020,7 @@ proxy-providers:
type: http # http 的 path 可空置,默认储存路径为 homedir 的 proxies 文件夹,文件名为 url 的 md5 type: http # http 的 path 可空置,默认储存路径为 homedir 的 proxies 文件夹,文件名为 url 的 md5
url: "url" url: "url"
interval: 3600 interval: 3600
path: ./provider1.yaml # 默认只允许存储在 mihomo 的 Home Dir如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1 path: ./provider1.yaml # 默认只允许存储在 mihomo 的 Home Dir如果想存储到其他位置,请通过设置 SAFE_PATHS 环境变量指定额外的安全路径。该环境变量的语法同本操作系统的PATH环境变量解析规则即Windows下以分号分割其他系统下以冒号分割
proxy: DIRECT proxy: DIRECT
# size-limit: 10240 # 限制下载文件最大为10kb默认为0即不限制文件大小 # size-limit: 10240 # 限制下载文件最大为10kb默认为0即不限制文件大小
header: header:
@@ -1077,7 +1077,7 @@ rule-providers:
rule1: rule1:
behavior: classical # domain ipcidr behavior: classical # domain ipcidr
interval: 259200 interval: 259200
path: /path/to/save/file.yaml # 默认只允许存储在 mihomo 的 Home Dir如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1 path: /path/to/save/file.yaml # 默认只允许存储在 mihomo 的 Home Dir如果想存储到其他位置,请通过设置 SAFE_PATHS 环境变量指定额外的安全路径。该环境变量的语法同本操作系统的PATH环境变量解析规则即Windows下以分号分割其他系统下以冒号分割
type: http # http 的 path 可空置,默认储存路径为 homedir 的 rules 文件夹,文件名为 url 的 md5 type: http # http 的 path 可空置,默认储存路径为 homedir 的 rules 文件夹,文件名为 url 的 md5
url: "url" url: "url"
proxy: DIRECT proxy: DIRECT
@@ -1276,6 +1276,16 @@ listeners:
# - 0123456789abcdef # - 0123456789abcdef
# server-names: # server-names:
# - test.com # - test.com
# #下列两个 limit 为选填可对未通过验证的回落连接限速bytesPerSec 默认为 0 即不启用
# #回落限速是一种特征,不建议启用,如果您是面板/一键脚本开发者,务必让这些参数随机化
# limit-fallback-upload:
# after-bytes: 0 # 传输指定字节后开始限速
# bytes-per-sec: 0 # 基准速率(字节/秒)
# burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
# limit-fallback-download:
# after-bytes: 0 # 传输指定字节后开始限速
# bytes-per-sec: 0 # 基准速率(字节/秒)
# burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
- name: tuic-in-1 - name: tuic-in-1
type: tuic type: tuic
@@ -1343,6 +1353,16 @@ listeners:
- 0123456789abcdef - 0123456789abcdef
server-names: server-names:
- test.com - test.com
#下列两个 limit 为选填可对未通过验证的回落连接限速bytesPerSec 默认为 0 即不启用
#回落限速是一种特征,不建议启用,如果您是面板/一键脚本开发者,务必让这些参数随机化
limit-fallback-upload:
after-bytes: 0 # 传输指定字节后开始限速
bytes-per-sec: 0 # 基准速率(字节/秒)
burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
limit-fallback-download:
after-bytes: 0 # 传输指定字节后开始限速
bytes-per-sec: 0 # 基准速率(字节/秒)
burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
### 注意对于vless listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 的其中一项 ### ### 注意对于vless listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 的其中一项 ###
- name: anytls-in-1 - name: anytls-in-1
@@ -1393,6 +1413,16 @@ listeners:
# - 0123456789abcdef # - 0123456789abcdef
# server-names: # server-names:
# - test.com # - test.com
# #下列两个 limit 为选填可对未通过验证的回落连接限速bytesPerSec 默认为 0 即不启用
# #回落限速是一种特征,不建议启用,如果您是面板/一键脚本开发者,务必让这些参数随机化
# limit-fallback-upload:
# after-bytes: 0 # 传输指定字节后开始限速
# bytes-per-sec: 0 # 基准速率(字节/秒)
# burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
# limit-fallback-download:
# after-bytes: 0 # 传输指定字节后开始限速
# bytes-per-sec: 0 # 基准速率(字节/秒)
# burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
# ss-option: # like trojan-go's `shadowsocks` config # ss-option: # like trojan-go's `shadowsocks` config
# enabled: false # enabled: false
# method: aes-128-gcm # aes-128-gcm/aes-256-gcm/chacha20-ietf-poly1305 # method: aes-128-gcm # aes-128-gcm/aes-256-gcm/chacha20-ietf-poly1305

12
go.mod
View File

@@ -20,23 +20,23 @@ require (
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab
github.com/metacubex/bart v0.20.5 github.com/metacubex/bart v0.20.5
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399
github.com/metacubex/chacha v0.1.2 github.com/metacubex/chacha v0.1.5
github.com/metacubex/fswatch v0.1.1 github.com/metacubex/fswatch v0.1.1
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639 github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639
github.com/metacubex/randv2 v0.2.0 github.com/metacubex/randv2 v0.2.0
github.com/metacubex/sing v0.5.3 github.com/metacubex/sing v0.5.4-0.20250605054047-54dc6097da29
github.com/metacubex/sing-mux v0.3.2 github.com/metacubex/sing-mux v0.3.2
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f
github.com/metacubex/sing-shadowsocks v0.2.10 github.com/metacubex/sing-shadowsocks v0.2.11-0.20250621023810-0e9ef9dd0c92
github.com/metacubex/sing-shadowsocks2 v0.2.4 github.com/metacubex/sing-shadowsocks2 v0.2.5-0.20250621023950-93d605a2143d
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c github.com/metacubex/sing-tun v0.4.7-0.20250611091011-60774779fdd8
github.com/metacubex/sing-vmess v0.2.2 github.com/metacubex/sing-vmess v0.2.2
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee github.com/metacubex/smux v0.0.0-20250503055512-501391591dee
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4
github.com/metacubex/utls v1.7.3 github.com/metacubex/utls v1.7.4-0.20250610022031-808d767c8c73
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181
github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20 github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20
github.com/mroth/weightedrand/v2 v2.1.0 github.com/mroth/weightedrand/v2 v2.1.0

28
go.sum
View File

@@ -101,8 +101,8 @@ github.com/metacubex/bart v0.20.5 h1:XkgLZ17QxfxkqKdGsojoM2Zu01mmHyyQSFzt2/calTM
github.com/metacubex/bart v0.20.5/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI= github.com/metacubex/bart v0.20.5/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig= github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro= github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
github.com/metacubex/chacha v0.1.2 h1:QulCq3eVm3TO6+4nVIWJtmSe7BT2GMrgVHuAoqRQnlc= github.com/metacubex/chacha v0.1.5 h1:fKWMb/5c7ZrY8Uoqi79PPFxl+qwR7X/q0OrsAubyX2M=
github.com/metacubex/chacha v0.1.2/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8= github.com/metacubex/chacha v0.1.5/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU= github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU=
github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI= github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
@@ -116,20 +116,24 @@ github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639/go.mod h1:Kc6
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs= github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w= github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing v0.5.3 h1:QWdN16WFKMk06x4nzkc8SvZ7y2x+TLQrpkPoHs+WSVM= github.com/metacubex/sing v0.5.4-0.20250605054047-54dc6097da29 h1:SD9q025FNTaepuFXFOKDhnGLVu6PQYChBvw2ZYPXeLo=
github.com/metacubex/sing v0.5.3/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w= github.com/metacubex/sing v0.5.4-0.20250605054047-54dc6097da29/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw= github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw=
github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw= github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f h1:mP3vIm+9hRFI0C0Vl3pE0NESF/L85FDbuB0tGgUii6I= github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f h1:mP3vIm+9hRFI0C0Vl3pE0NESF/L85FDbuB0tGgUii6I=
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM= github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
github.com/metacubex/sing-shadowsocks v0.2.10 h1:Pr7LDbjMANIQHl07zWgl1vDuhpsfDQUpZ8cX6DPabfg= github.com/metacubex/sing-shadowsocks v0.2.11-0.20250621021503-4f85ef9bf4b3 h1:dtiRj7WaCAXp4UhCkmaIiFF6v886qXiuqeIDN4Z//9E=
github.com/metacubex/sing-shadowsocks v0.2.10/go.mod h1:MtRM0ZZjR0kaDOzy9zWSt6/4/UlrnsNBq+1FNAF4vBk= github.com/metacubex/sing-shadowsocks v0.2.11-0.20250621021503-4f85ef9bf4b3/go.mod h1:/squZ38pXrYjqtg8qn+joVvwbpGNYQNp8yxKsMVbCto=
github.com/metacubex/sing-shadowsocks2 v0.2.4 h1:Ec0x3hHR7xkld5Z09IGh16wtUUpBb2HgqZ9DExd8Q7s= github.com/metacubex/sing-shadowsocks v0.2.11-0.20250621023810-0e9ef9dd0c92 h1:Y9ebcKya6ow7VHoESCN5+l4zZvg5eaL2IhI5LLCQxQA=
github.com/metacubex/sing-shadowsocks2 v0.2.4/go.mod h1:WP8+S0kqtnSbX1vlIpo5i8Irm/ijZITEPBcZ26B5unY= github.com/metacubex/sing-shadowsocks v0.2.11-0.20250621023810-0e9ef9dd0c92/go.mod h1:/squZ38pXrYjqtg8qn+joVvwbpGNYQNp8yxKsMVbCto=
github.com/metacubex/sing-shadowsocks2 v0.2.5-0.20250621021638-dcd503063651 h1:vwLj0DDjPYy4AHEZvfRVf8ih52o6wpBnJxXxqa+ztmE=
github.com/metacubex/sing-shadowsocks2 v0.2.5-0.20250621021638-dcd503063651/go.mod h1:+ukTd0OPFglT3bnKAYTJWYPbuox6HYNXE235r5tHdUk=
github.com/metacubex/sing-shadowsocks2 v0.2.5-0.20250621023950-93d605a2143d h1:Ey3A1tA8lVkRbK1FDmwuWj/57Nr8JMdpoVqe45mFzJg=
github.com/metacubex/sing-shadowsocks2 v0.2.5-0.20250621023950-93d605a2143d/go.mod h1:+ukTd0OPFglT3bnKAYTJWYPbuox6HYNXE235r5tHdUk=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c h1:Y6jk7AH5BEg9Dsvczrf/KokYsvxeKSZZlCLHg+hC4ro= github.com/metacubex/sing-tun v0.4.7-0.20250611091011-60774779fdd8 h1:4zWKqxTx75TbfW2EmlQ3hxM6RTRg2PYOAVMCnU4I61I=
github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U= github.com/metacubex/sing-tun v0.4.7-0.20250611091011-60774779fdd8/go.mod h1:2YywXPWW8Z97kTH7RffOeykKzU+l0aiKlglWV1PAS64=
github.com/metacubex/sing-vmess v0.2.2 h1:nG6GIKF1UOGmlzs+BIetdGHkFZ20YqFVIYp5Htqzp+4= github.com/metacubex/sing-vmess v0.2.2 h1:nG6GIKF1UOGmlzs+BIetdGHkFZ20YqFVIYp5Htqzp+4=
github.com/metacubex/sing-vmess v0.2.2/go.mod h1:CVDNcdSLVYFgTHQlubr88d8CdqupAUDqLjROos+H9xk= github.com/metacubex/sing-vmess v0.2.2/go.mod h1:CVDNcdSLVYFgTHQlubr88d8CdqupAUDqLjROos+H9xk=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
@@ -138,8 +142,8 @@ github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113a
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE= github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nUbSikx9OGdu/3AgFDqgcLj4GoqyQkc= github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nUbSikx9OGdu/3AgFDqgcLj4GoqyQkc=
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.7.3 h1:yDcMEWojFh+t8rU9X0HPcZDPAoFze/rIIyssqivzj8A= github.com/metacubex/utls v1.7.4-0.20250610022031-808d767c8c73 h1:HWKsf92BqLYqugATFIJ3hYiEBZ7JF6AoqyvqF39afuI=
github.com/metacubex/utls v1.7.3/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU= github.com/metacubex/utls v1.7.4-0.20250610022031-808d767c8c73/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ= github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y= github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=

View File

@@ -75,6 +75,7 @@ type tunSchema struct {
AutoRedirect *bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"` AutoRedirect *bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"`
AutoRedirectInputMark *uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"` AutoRedirectInputMark *uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"`
AutoRedirectOutputMark *uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"` AutoRedirectOutputMark *uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"`
LoopbackAddress *[]netip.Addr `yaml:"loopback-address" json:"loopback-address,omitempty"`
StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"` StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"`
RouteAddress *[]netip.Prefix `yaml:"route-address" json:"route-address,omitempty"` RouteAddress *[]netip.Prefix `yaml:"route-address" json:"route-address,omitempty"`
RouteAddressSet *[]string `yaml:"route-address-set" json:"route-address-set,omitempty"` RouteAddressSet *[]string `yaml:"route-address-set" json:"route-address-set,omitempty"`
@@ -174,6 +175,9 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun {
if p.AutoRedirectOutputMark != nil { if p.AutoRedirectOutputMark != nil {
def.AutoRedirectOutputMark = *p.AutoRedirectOutputMark def.AutoRedirectOutputMark = *p.AutoRedirectOutputMark
} }
if p.LoopbackAddress != nil {
def.LoopbackAddress = *p.LoopbackAddress
}
if p.StrictRoute != nil { if p.StrictRoute != nil {
def.StrictRoute = *p.StrictRoute def.StrictRoute = *p.StrictRoute
} }

View File

@@ -9,18 +9,6 @@ import (
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
func StringSliceToNetipPrefixSlice(ss []string) ([]netip.Prefix, error) {
lps := make([]netip.Prefix, 0, len(ss))
for _, s := range ss {
prefix, err := netip.ParsePrefix(s)
if err != nil {
return nil, err
}
lps = append(lps, prefix)
}
return lps, nil
}
type Tun struct { type Tun struct {
Enable bool `yaml:"enable" json:"enable"` Enable bool `yaml:"enable" json:"enable"`
Device string `yaml:"device" json:"device"` Device string `yaml:"device" json:"device"`
@@ -39,6 +27,7 @@ type Tun struct {
AutoRedirect bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"` AutoRedirect bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"`
AutoRedirectInputMark uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"` AutoRedirectInputMark uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"`
AutoRedirectOutputMark uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"` AutoRedirectOutputMark uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"`
LoopbackAddress []netip.Addr `yaml:"loopback-address" json:"loopback-address,omitempty"`
StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"` StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"`
RouteAddress []netip.Prefix `yaml:"route-address" json:"route-address,omitempty"` RouteAddress []netip.Prefix `yaml:"route-address" json:"route-address,omitempty"`
RouteAddressSet []string `yaml:"route-address-set" json:"route-address-set,omitempty"` RouteAddressSet []string `yaml:"route-address-set" json:"route-address-set,omitempty"`
@@ -142,6 +131,9 @@ func (t *Tun) Equal(other Tun) bool {
if t.AutoRedirectOutputMark != other.AutoRedirectOutputMark { if t.AutoRedirectOutputMark != other.AutoRedirectOutputMark {
return false return false
} }
if !slices.Equal(t.RouteAddress, other.RouteAddress) {
return false
}
if t.StrictRoute != other.StrictRoute { if t.StrictRoute != other.StrictRoute {
return false return false
} }

View File

@@ -9,6 +9,15 @@ type RealityConfig struct {
ServerNames []string `inbound:"server-names"` ServerNames []string `inbound:"server-names"`
MaxTimeDifference int `inbound:"max-time-difference,omitempty"` MaxTimeDifference int `inbound:"max-time-difference,omitempty"`
Proxy string `inbound:"proxy,omitempty"` Proxy string `inbound:"proxy,omitempty"`
LimitFallbackUpload RealityLimitFallback `inbound:"limit-fallback-upload,omitempty"`
LimitFallbackDownload RealityLimitFallback `inbound:"limit-fallback-download,omitempty"`
}
type RealityLimitFallback struct {
AfterBytes uint64 `inbound:"after-bytes,omitempty"`
BytesPerSec uint64 `inbound:"bytes-per-sec,omitempty"`
BurstBytesPerSec uint64 `inbound:"burst-bytes-per-sec,omitempty"`
} }
func (c RealityConfig) Build() reality.Config { func (c RealityConfig) Build() reality.Config {
@@ -19,5 +28,16 @@ func (c RealityConfig) Build() reality.Config {
ServerNames: c.ServerNames, ServerNames: c.ServerNames,
MaxTimeDifference: c.MaxTimeDifference, MaxTimeDifference: c.MaxTimeDifference,
Proxy: c.Proxy, Proxy: c.Proxy,
LimitFallbackUpload: reality.LimitFallback{
AfterBytes: c.LimitFallbackUpload.AfterBytes,
BytesPerSec: c.LimitFallbackUpload.BytesPerSec,
BurstBytesPerSec: c.LimitFallbackUpload.BurstBytesPerSec,
},
LimitFallbackDownload: reality.LimitFallback{
AfterBytes: c.LimitFallbackDownload.AfterBytes,
BytesPerSec: c.LimitFallbackDownload.BytesPerSec,
BurstBytesPerSec: c.LimitFallbackDownload.BurstBytesPerSec,
},
} }
} }

View File

@@ -1,8 +1,8 @@
package inbound package inbound
import ( import (
"errors" "encoding"
"strings" "net/netip"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config" LC "github.com/metacubex/mihomo/listener/config"
@@ -12,50 +12,55 @@ import (
type TunOption struct { type TunOption struct {
BaseOption BaseOption
Device string `inbound:"device,omitempty"` Device string `inbound:"device,omitempty"`
Stack string `inbound:"stack,omitempty"` Stack C.TUNStack `inbound:"stack,omitempty"`
DNSHijack []string `inbound:"dns-hijack,omitempty"` DNSHijack []string `inbound:"dns-hijack,omitempty"`
AutoRoute bool `inbound:"auto-route,omitempty"` AutoRoute bool `inbound:"auto-route,omitempty"`
AutoDetectInterface bool `inbound:"auto-detect-interface,omitempty"` AutoDetectInterface bool `inbound:"auto-detect-interface,omitempty"`
MTU uint32 `inbound:"mtu,omitempty"` MTU uint32 `inbound:"mtu,omitempty"`
GSO bool `inbound:"gso,omitempty"` GSO bool `inbound:"gso,omitempty"`
GSOMaxSize uint32 `inbound:"gso-max-size,omitempty"` GSOMaxSize uint32 `inbound:"gso-max-size,omitempty"`
Inet4Address []string `inbound:"inet4-address,omitempty"` Inet4Address []netip.Prefix `inbound:"inet4-address,omitempty"`
Inet6Address []string `inbound:"inet6-address,omitempty"` Inet6Address []netip.Prefix `inbound:"inet6-address,omitempty"`
IPRoute2TableIndex int `inbound:"iproute2-table-index,omitempty"` IPRoute2TableIndex int `inbound:"iproute2-table-index,omitempty"`
IPRoute2RuleIndex int `inbound:"iproute2-rule-index,omitempty"` IPRoute2RuleIndex int `inbound:"iproute2-rule-index,omitempty"`
AutoRedirect bool `inbound:"auto-redirect,omitempty"` AutoRedirect bool `inbound:"auto-redirect,omitempty"`
AutoRedirectInputMark uint32 `inbound:"auto-redirect-input-mark,omitempty"` AutoRedirectInputMark uint32 `inbound:"auto-redirect-input-mark,omitempty"`
AutoRedirectOutputMark uint32 `inbound:"auto-redirect-output-mark,omitempty"` AutoRedirectOutputMark uint32 `inbound:"auto-redirect-output-mark,omitempty"`
StrictRoute bool `inbound:"strict-route,omitempty"` LoopbackAddress []netip.Addr `inbound:"loopback-address,omitempty"`
RouteAddress []string `inbound:"route-address,omitempty"` StrictRoute bool `inbound:"strict-route,omitempty"`
RouteAddressSet []string `inbound:"route-address-set,omitempty"` RouteAddress []netip.Prefix `inbound:"route-address,omitempty"`
RouteExcludeAddress []string `inbound:"route-exclude-address,omitempty"` RouteAddressSet []string `inbound:"route-address-set,omitempty"`
RouteExcludeAddressSet []string `inbound:"route-exclude-address-set,omitempty"` RouteExcludeAddress []netip.Prefix `inbound:"route-exclude-address,omitempty"`
IncludeInterface []string `inbound:"include-interface,omitempty"` RouteExcludeAddressSet []string `inbound:"route-exclude-address-set,omitempty"`
ExcludeInterface []string `inbound:"exclude-interface,omitempty"` IncludeInterface []string `inbound:"include-interface,omitempty"`
IncludeUID []uint32 `inbound:"include-uid,omitempty"` ExcludeInterface []string `inbound:"exclude-interface,omitempty"`
IncludeUIDRange []string `inbound:"include-uid-range,omitempty"` IncludeUID []uint32 `inbound:"include-uid,omitempty"`
ExcludeUID []uint32 `inbound:"exclude-uid,omitempty"` IncludeUIDRange []string `inbound:"include-uid-range,omitempty"`
ExcludeUIDRange []string `inbound:"exclude-uid-range,omitempty"` ExcludeUID []uint32 `inbound:"exclude-uid,omitempty"`
ExcludeSrcPort []uint16 `inbound:"exclude-src-port,omitempty"` ExcludeUIDRange []string `inbound:"exclude-uid-range,omitempty"`
ExcludeSrcPortRange []string `inbound:"exclude-src-port-range,omitempty"` ExcludeSrcPort []uint16 `inbound:"exclude-src-port,omitempty"`
ExcludeDstPort []uint16 `inbound:"exclude-dst-port,omitempty"` ExcludeSrcPortRange []string `inbound:"exclude-src-port-range,omitempty"`
ExcludeDstPortRange []string `inbound:"exclude-dst-port-range,omitempty"` ExcludeDstPort []uint16 `inbound:"exclude-dst-port,omitempty"`
IncludeAndroidUser []int `inbound:"include-android-user,omitempty"` ExcludeDstPortRange []string `inbound:"exclude-dst-port-range,omitempty"`
IncludePackage []string `inbound:"include-package,omitempty"` IncludeAndroidUser []int `inbound:"include-android-user,omitempty"`
ExcludePackage []string `inbound:"exclude-package,omitempty"` IncludePackage []string `inbound:"include-package,omitempty"`
EndpointIndependentNat bool `inbound:"endpoint-independent-nat,omitempty"` ExcludePackage []string `inbound:"exclude-package,omitempty"`
UDPTimeout int64 `inbound:"udp-timeout,omitempty"` EndpointIndependentNat bool `inbound:"endpoint-independent-nat,omitempty"`
FileDescriptor int `inbound:"file-descriptor,omitempty"` UDPTimeout int64 `inbound:"udp-timeout,omitempty"`
FileDescriptor int `inbound:"file-descriptor,omitempty"`
Inet4RouteAddress []string `inbound:"inet4-route-address,omitempty"` Inet4RouteAddress []netip.Prefix `inbound:"inet4-route-address,omitempty"`
Inet6RouteAddress []string `inbound:"inet6-route-address,omitempty"` Inet6RouteAddress []netip.Prefix `inbound:"inet6-route-address,omitempty"`
Inet4RouteExcludeAddress []string `inbound:"inet4-route-exclude-address,omitempty"` Inet4RouteExcludeAddress []netip.Prefix `inbound:"inet4-route-exclude-address,omitempty"`
Inet6RouteExcludeAddress []string `inbound:"inet6-route-exclude-address,omitempty"` Inet6RouteExcludeAddress []netip.Prefix `inbound:"inet6-route-exclude-address,omitempty"`
} }
var _ encoding.TextUnmarshaler = (*netip.Addr)(nil) // ensure netip.Addr can decode direct by structure package
var _ encoding.TextUnmarshaler = (*netip.Prefix)(nil) // ensure netip.Prefix can decode direct by structure package
var _ encoding.TextUnmarshaler = (*C.TUNStack)(nil) // ensure C.TUNStack can decode direct by structure package
func (o TunOption) Equal(config C.InboundConfig) bool { func (o TunOption) Equal(config C.InboundConfig) bool {
return optionToString(o) == optionToString(config) return optionToString(o) == optionToString(config)
} }
@@ -72,68 +77,31 @@ func NewTun(options *TunOption) (*Tun, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
stack, exist := C.StackTypeMapping[strings.ToLower(options.Stack)]
if !exist {
return nil, errors.New("invalid tun stack")
}
routeAddress, err := LC.StringSliceToNetipPrefixSlice(options.RouteAddress)
if err != nil {
return nil, err
}
routeExcludeAddress, err := LC.StringSliceToNetipPrefixSlice(options.RouteExcludeAddress)
if err != nil {
return nil, err
}
inet4Address, err := LC.StringSliceToNetipPrefixSlice(options.Inet4Address)
if err != nil {
return nil, err
}
inet6Address, err := LC.StringSliceToNetipPrefixSlice(options.Inet6Address)
if err != nil {
return nil, err
}
inet4RouteAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet4RouteAddress)
if err != nil {
return nil, err
}
inet6RouteAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet6RouteAddress)
if err != nil {
return nil, err
}
inet4RouteExcludeAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet4RouteExcludeAddress)
if err != nil {
return nil, err
}
inet6RouteExcludeAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet6RouteExcludeAddress)
if err != nil {
return nil, err
}
return &Tun{ return &Tun{
Base: base, Base: base,
config: options, config: options,
tun: LC.Tun{ tun: LC.Tun{
Enable: true, Enable: true,
Device: options.Device, Device: options.Device,
Stack: stack, Stack: options.Stack,
DNSHijack: options.DNSHijack, DNSHijack: options.DNSHijack,
AutoRoute: options.AutoRoute, AutoRoute: options.AutoRoute,
AutoDetectInterface: options.AutoDetectInterface, AutoDetectInterface: options.AutoDetectInterface,
MTU: options.MTU, MTU: options.MTU,
GSO: options.GSO, GSO: options.GSO,
GSOMaxSize: options.GSOMaxSize, GSOMaxSize: options.GSOMaxSize,
Inet4Address: inet4Address, Inet4Address: options.Inet4Address,
Inet6Address: inet6Address, Inet6Address: options.Inet6Address,
IPRoute2TableIndex: options.IPRoute2TableIndex, IPRoute2TableIndex: options.IPRoute2TableIndex,
IPRoute2RuleIndex: options.IPRoute2RuleIndex, IPRoute2RuleIndex: options.IPRoute2RuleIndex,
AutoRedirect: options.AutoRedirect, AutoRedirect: options.AutoRedirect,
AutoRedirectInputMark: options.AutoRedirectInputMark, AutoRedirectInputMark: options.AutoRedirectInputMark,
AutoRedirectOutputMark: options.AutoRedirectOutputMark, AutoRedirectOutputMark: options.AutoRedirectOutputMark,
LoopbackAddress: options.LoopbackAddress,
StrictRoute: options.StrictRoute, StrictRoute: options.StrictRoute,
RouteAddress: routeAddress, RouteAddress: options.RouteAddress,
RouteAddressSet: options.RouteAddressSet, RouteAddressSet: options.RouteAddressSet,
RouteExcludeAddress: routeExcludeAddress, RouteExcludeAddress: options.RouteExcludeAddress,
RouteExcludeAddressSet: options.RouteExcludeAddressSet, RouteExcludeAddressSet: options.RouteExcludeAddressSet,
IncludeInterface: options.IncludeInterface, IncludeInterface: options.IncludeInterface,
ExcludeInterface: options.ExcludeInterface, ExcludeInterface: options.ExcludeInterface,
@@ -152,10 +120,10 @@ func NewTun(options *TunOption) (*Tun, error) {
UDPTimeout: options.UDPTimeout, UDPTimeout: options.UDPTimeout,
FileDescriptor: options.FileDescriptor, FileDescriptor: options.FileDescriptor,
Inet4RouteAddress: inet4RouteAddress, Inet4RouteAddress: options.Inet4RouteAddress,
Inet6RouteAddress: inet6RouteAddress, Inet6RouteAddress: options.Inet6RouteAddress,
Inet4RouteExcludeAddress: inet4RouteExcludeAddress, Inet4RouteExcludeAddress: options.Inet4RouteExcludeAddress,
Inet6RouteExcludeAddress: inet6RouteExcludeAddress, Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress,
}, },
}, nil }, nil
} }

View File

@@ -64,7 +64,7 @@ func ParseListener(mapping map[string]any) (C.InboundListener, error) {
listener, err = IN.NewTunnel(tunnelOption) listener, err = IN.NewTunnel(tunnelOption)
case "tun": case "tun":
tunOption := &IN.TunOption{ tunOption := &IN.TunOption{
Stack: C.TunGvisor.String(), Stack: C.TunGvisor,
DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query
} }
err = decoder.Decode(mapping, tunOption) err = decoder.Decode(mapping, tunOption)

View File

@@ -20,6 +20,7 @@ import (
) )
type Conn = utls.Conn type Conn = utls.Conn
type LimitFallback = utls.RealityLimitFallback
type Config struct { type Config struct {
Dest string Dest string
@@ -28,6 +29,9 @@ type Config struct {
ServerNames []string ServerNames []string
MaxTimeDifference int MaxTimeDifference int
Proxy string Proxy string
LimitFallbackUpload LimitFallback
LimitFallbackDownload LimitFallback
} }
func (c Config) Build(tunnel C.Tunnel) (*Builder, error) { func (c Config) Build(tunnel C.Tunnel) (*Builder, error) {
@@ -73,6 +77,9 @@ func (c Config) Build(tunnel C.Tunnel) (*Builder, error) {
return inner.HandleTcp(tunnel, address, c.Proxy) return inner.HandleTcp(tunnel, address, c.Proxy)
} }
realityConfig.LimitFallbackUpload = c.LimitFallbackUpload
realityConfig.LimitFallbackDownload = c.LimitFallbackDownload
return &Builder{realityConfig}, nil return &Builder{realityConfig}, nil
} }

View File

@@ -347,6 +347,8 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
IPRoute2RuleIndex: ruleIndex, IPRoute2RuleIndex: ruleIndex,
AutoRedirectInputMark: inputMark, AutoRedirectInputMark: inputMark,
AutoRedirectOutputMark: outputMark, AutoRedirectOutputMark: outputMark,
Inet4LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is4),
Inet6LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is6),
StrictRoute: options.StrictRoute, StrictRoute: options.StrictRoute,
Inet4RouteAddress: inet4RouteAddress, Inet4RouteAddress: inet4RouteAddress,
Inet6RouteAddress: inet6RouteAddress, Inet6RouteAddress: inet6RouteAddress,

View File

@@ -1,7 +1,6 @@
package log package log
import ( import (
"encoding/json"
"errors" "errors"
"strings" "strings"
) )
@@ -25,30 +24,6 @@ const (
type LogLevel int type LogLevel int
// UnmarshalYAML unserialize LogLevel with yaml
func (l *LogLevel) UnmarshalYAML(unmarshal func(any) error) error {
var tp string
unmarshal(&tp)
level, exist := LogLevelMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid log-level")
}
*l = level
return nil
}
// UnmarshalJSON unserialize LogLevel with json
func (l *LogLevel) UnmarshalJSON(data []byte) error {
var tp string
json.Unmarshal(data, &tp)
level, exist := LogLevelMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid log-level")
}
*l = level
return nil
}
// UnmarshalText unserialize LogLevel // UnmarshalText unserialize LogLevel
func (l *LogLevel) UnmarshalText(data []byte) error { func (l *LogLevel) UnmarshalText(data []byte) error {
level, exist := LogLevelMapping[strings.ToLower(string(data))] level, exist := LogLevelMapping[strings.ToLower(string(data))]
@@ -59,16 +34,6 @@ func (l *LogLevel) UnmarshalText(data []byte) error {
return nil return nil
} }
// MarshalYAML serialize LogLevel with yaml
func (l LogLevel) MarshalYAML() (any, error) {
return l.String(), nil
}
// MarshalJSON serialize LogLevel with json
func (l LogLevel) MarshalJSON() ([]byte, error) {
return json.Marshal(l.String())
}
// MarshalText serialize LogLevel // MarshalText serialize LogLevel
func (l LogLevel) MarshalText() ([]byte, error) { func (l LogLevel) MarshalText() ([]byte, error) {
return []byte(l.String()), nil return []byte(l.String()), nil

View File

@@ -21,14 +21,6 @@ var (
type Base struct { type Base struct {
} }
func (b *Base) ShouldFindProcess() bool {
return false
}
func (b *Base) ShouldResolveIP() bool {
return false
}
func (b *Base) ProviderNames() []string { return nil } func (b *Base) ProviderNames() []string { return nil }
func ParseParams(params []string) (isSrc bool, noResolve bool) { func ParseParams(params []string) (isSrc bool, noResolve bool) {

View File

@@ -17,7 +17,7 @@ func (d *Domain) RuleType() C.RuleType {
return C.Domain return C.Domain
} }
func (d *Domain) Match(metadata *C.Metadata) (bool, string) { func (d *Domain) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
return metadata.RuleHost() == d.domain, d.adapter return metadata.RuleHost() == d.domain, d.adapter
} }

View File

@@ -17,7 +17,7 @@ func (dk *DomainKeyword) RuleType() C.RuleType {
return C.DomainKeyword return C.DomainKeyword
} }
func (dk *DomainKeyword) Match(metadata *C.Metadata) (bool, string) { func (dk *DomainKeyword) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
domain := metadata.RuleHost() domain := metadata.RuleHost()
return strings.Contains(domain, dk.keyword), dk.adapter return strings.Contains(domain, dk.keyword), dk.adapter
} }

View File

@@ -16,7 +16,7 @@ func (dr *DomainRegex) RuleType() C.RuleType {
return C.DomainRegex return C.DomainRegex
} }
func (dr *DomainRegex) Match(metadata *C.Metadata) (bool, string) { func (dr *DomainRegex) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
domain := metadata.RuleHost() domain := metadata.RuleHost()
match, _ := dr.regex.MatchString(domain) match, _ := dr.regex.MatchString(domain)
return match, dr.adapter return match, dr.adapter

View File

@@ -17,7 +17,7 @@ func (ds *DomainSuffix) RuleType() C.RuleType {
return C.DomainSuffix return C.DomainSuffix
} }
func (ds *DomainSuffix) Match(metadata *C.Metadata) (bool, string) { func (ds *DomainSuffix) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
domain := metadata.RuleHost() domain := metadata.RuleHost()
return strings.HasSuffix(domain, "."+ds.suffix) || domain == ds.suffix, ds.adapter return strings.HasSuffix(domain, "."+ds.suffix) || domain == ds.suffix, ds.adapter
} }

View File

@@ -18,7 +18,7 @@ func (d *DSCP) RuleType() C.RuleType {
return C.DSCP return C.DSCP
} }
func (d *DSCP) Match(metadata *C.Metadata) (bool, string) { func (d *DSCP) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
return d.ranges.Check(metadata.DSCP), d.adapter return d.ranges.Check(metadata.DSCP), d.adapter
} }

View File

@@ -13,7 +13,7 @@ func (f *Match) RuleType() C.RuleType {
return C.MATCH return C.MATCH
} }
func (f *Match) Match(metadata *C.Metadata) (bool, string) { func (f *Match) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
return true, f.adapter return true, f.adapter
} }

View File

@@ -33,7 +33,11 @@ func (g *GEOIP) RuleType() C.RuleType {
return C.GEOIP return C.GEOIP
} }
func (g *GEOIP) Match(metadata *C.Metadata) (bool, string) { func (g *GEOIP) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
if !g.noResolveIP && !g.isSourceIP && helper.ResolveIP != nil {
helper.ResolveIP()
}
ip := metadata.DstIP ip := metadata.DstIP
if g.isSourceIP { if g.isSourceIP {
ip = metadata.SrcIP ip = metadata.SrcIP
@@ -161,10 +165,6 @@ func (g *GEOIP) Payload() string {
return g.country return g.country
} }
func (g *GEOIP) ShouldResolveIP() bool {
return !g.noResolveIP
}
func (g *GEOIP) GetCountry() string { func (g *GEOIP) GetCountry() string {
return g.country return g.country
} }

View File

@@ -22,7 +22,7 @@ func (gs *GEOSITE) RuleType() C.RuleType {
return C.GEOSITE return C.GEOSITE
} }
func (gs *GEOSITE) Match(metadata *C.Metadata) (bool, string) { func (gs *GEOSITE) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
return gs.MatchDomain(metadata.RuleHost()), gs.adapter return gs.MatchDomain(metadata.RuleHost()), gs.adapter
} }

View File

@@ -2,8 +2,9 @@ package common
import ( import (
"fmt" "fmt"
C "github.com/metacubex/mihomo/constant"
"strings" "strings"
C "github.com/metacubex/mihomo/constant"
) )
type InName struct { type InName struct {
@@ -13,7 +14,7 @@ type InName struct {
payload string payload string
} }
func (u *InName) Match(metadata *C.Metadata) (bool, string) { func (u *InName) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
for _, name := range u.names { for _, name := range u.names {
if metadata.InName == name { if metadata.InName == name {
return true, u.adapter return true, u.adapter
@@ -36,8 +37,12 @@ func (u *InName) Payload() string {
func NewInName(iNames, adapter string) (*InName, error) { func NewInName(iNames, adapter string) (*InName, error) {
names := strings.Split(iNames, "/") names := strings.Split(iNames, "/")
if len(names) == 0 { for i, name := range names {
return nil, fmt.Errorf("in name couldn't be empty") name = strings.TrimSpace(name)
if len(name) == 0 {
return nil, fmt.Errorf("in name couldn't be empty")
}
names[i] = name
} }
return &InName{ return &InName{

View File

@@ -2,8 +2,9 @@ package common
import ( import (
"fmt" "fmt"
C "github.com/metacubex/mihomo/constant"
"strings" "strings"
C "github.com/metacubex/mihomo/constant"
) )
type InType struct { type InType struct {
@@ -13,7 +14,7 @@ type InType struct {
payload string payload string
} }
func (u *InType) Match(metadata *C.Metadata) (bool, string) { func (u *InType) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
for _, tp := range u.types { for _, tp := range u.types {
if metadata.Type == tp { if metadata.Type == tp {
return true, u.adapter return true, u.adapter
@@ -36,8 +37,12 @@ func (u *InType) Payload() string {
func NewInType(iTypes, adapter string) (*InType, error) { func NewInType(iTypes, adapter string) (*InType, error) {
types := strings.Split(iTypes, "/") types := strings.Split(iTypes, "/")
if len(types) == 0 { for i, tp := range types {
return nil, fmt.Errorf("in type couldn't be empty") tp = strings.TrimSpace(tp)
if len(tp) == 0 {
return nil, fmt.Errorf("in type couldn't be empty")
}
types[i] = tp
} }
tps, err := parseInTypes(types) tps, err := parseInTypes(types)

View File

@@ -2,8 +2,9 @@ package common
import ( import (
"fmt" "fmt"
C "github.com/metacubex/mihomo/constant"
"strings" "strings"
C "github.com/metacubex/mihomo/constant"
) )
type InUser struct { type InUser struct {
@@ -13,7 +14,7 @@ type InUser struct {
payload string payload string
} }
func (u *InUser) Match(metadata *C.Metadata) (bool, string) { func (u *InUser) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
for _, user := range u.users { for _, user := range u.users {
if metadata.InUser == user { if metadata.InUser == user {
return true, u.adapter return true, u.adapter
@@ -36,8 +37,12 @@ func (u *InUser) Payload() string {
func NewInUser(iUsers, adapter string) (*InUser, error) { func NewInUser(iUsers, adapter string) (*InUser, error) {
users := strings.Split(iUsers, "/") users := strings.Split(iUsers, "/")
if len(users) == 0 { for i, user := range users {
return nil, fmt.Errorf("in user couldn't be empty") user = strings.TrimSpace(user)
if len(user) == 0 {
return nil, fmt.Errorf("in user couldn't be empty")
}
users[i] = user
} }
return &InUser{ return &InUser{

View File

@@ -15,7 +15,11 @@ type ASN struct {
isSourceIP bool isSourceIP bool
} }
func (a *ASN) Match(metadata *C.Metadata) (bool, string) { func (a *ASN) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
if !a.noResolveIP && !a.isSourceIP && helper.ResolveIP != nil {
helper.ResolveIP()
}
ip := metadata.DstIP ip := metadata.DstIP
if a.isSourceIP { if a.isSourceIP {
ip = metadata.SrcIP ip = metadata.SrcIP
@@ -49,10 +53,6 @@ func (a *ASN) Payload() string {
return a.asn return a.asn
} }
func (a *ASN) ShouldResolveIP() bool {
return !a.noResolveIP
}
func (a *ASN) GetASN() string { func (a *ASN) GetASN() string {
return a.asn return a.asn
} }

View File

@@ -35,7 +35,11 @@ func (i *IPCIDR) RuleType() C.RuleType {
return C.IPCIDR return C.IPCIDR
} }
func (i *IPCIDR) Match(metadata *C.Metadata) (bool, string) { func (i *IPCIDR) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
if !i.noResolveIP && !i.isSourceIP && helper.ResolveIP != nil {
helper.ResolveIP()
}
ip := metadata.DstIP ip := metadata.DstIP
if i.isSourceIP { if i.isSourceIP {
ip = metadata.SrcIP ip = metadata.SrcIP
@@ -51,10 +55,6 @@ func (i *IPCIDR) Payload() string {
return i.ipnet.String() return i.ipnet.String()
} }
func (i *IPCIDR) ShouldResolveIP() bool {
return !i.noResolveIP
}
func NewIPCIDR(s string, adapter string, opts ...IPCIDROption) (*IPCIDR, error) { func NewIPCIDR(s string, adapter string, opts ...IPCIDROption) (*IPCIDR, error) {
ipnet, err := netip.ParsePrefix(s) ipnet, err := netip.ParsePrefix(s)
if err != nil { if err != nil {

View File

@@ -22,7 +22,11 @@ func (is *IPSuffix) RuleType() C.RuleType {
return C.IPSuffix return C.IPSuffix
} }
func (is *IPSuffix) Match(metadata *C.Metadata) (bool, string) { func (is *IPSuffix) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
if !is.noResolveIP && !is.isSourceIP && helper.ResolveIP != nil {
helper.ResolveIP()
}
ip := metadata.DstIP ip := metadata.DstIP
if is.isSourceIP { if is.isSourceIP {
ip = metadata.SrcIP ip = metadata.SrcIP
@@ -57,10 +61,6 @@ func (is *IPSuffix) Payload() string {
return is.payload return is.payload
} }
func (is *IPSuffix) ShouldResolveIP() bool {
return !is.noResolveIP
}
func NewIPSuffix(payload, adapter string, isSrc, noResolveIP bool) (*IPSuffix, error) { func NewIPSuffix(payload, adapter string, isSrc, noResolveIP bool) (*IPSuffix, error) {
ipnet, err := netip.ParsePrefix(payload) ipnet, err := netip.ParsePrefix(payload)
if err != nil { if err != nil {

View File

@@ -34,7 +34,7 @@ func (n *NetworkType) RuleType() C.RuleType {
return C.Network return C.Network
} }
func (n *NetworkType) Match(metadata *C.Metadata) (bool, string) { func (n *NetworkType) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
return n.network == metadata.NetWork, n.adapter return n.network == metadata.NetWork, n.adapter
} }

View File

@@ -19,7 +19,7 @@ func (p *Port) RuleType() C.RuleType {
return p.ruleType return p.ruleType
} }
func (p *Port) Match(metadata *C.Metadata) (bool, string) { func (p *Port) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
targetPort := metadata.DstPort targetPort := metadata.DstPort
switch p.ruleType { switch p.ruleType {
case C.InPort: case C.InPort:

View File

@@ -30,7 +30,10 @@ func (ps *Process) RuleType() C.RuleType {
return C.ProcessPath return C.ProcessPath
} }
func (ps *Process) Match(metadata *C.Metadata) (bool, string) { func (ps *Process) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
if helper.FindProcess != nil {
helper.FindProcess()
}
if ps.nameOnly { if ps.nameOnly {
if ps.regexp != nil { if ps.regexp != nil {
match, _ := ps.regexp.MatchString(metadata.Process) match, _ := ps.regexp.MatchString(metadata.Process)
@@ -54,10 +57,6 @@ func (ps *Process) Payload() string {
return ps.process return ps.process
} }
func (ps *Process) ShouldFindProcess() bool {
return true
}
func NewProcess(process string, adapter string, nameOnly bool, regex bool) (*Process, error) { func NewProcess(process string, adapter string, nameOnly bool, regex bool) (*Process, error) {
var r *regexp2.Regexp var r *regexp2.Regexp
var err error var err error

View File

@@ -41,7 +41,10 @@ func (u *Uid) RuleType() C.RuleType {
return C.Uid return C.Uid
} }
func (u *Uid) Match(metadata *C.Metadata) (bool, string) { func (u *Uid) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
if helper.FindProcess != nil {
helper.FindProcess()
}
if metadata.Uid != 0 { if metadata.Uid != 0 {
if u.uids.Check(metadata.Uid) { if u.uids.Check(metadata.Uid) {
return true, u.adapter return true, u.adapter
@@ -58,7 +61,3 @@ func (u *Uid) Adapter() string {
func (u *Uid) Payload() string { func (u *Uid) Payload() string {
return u.oUid return u.oUid
} }
func (u *Uid) ShouldFindProcess() bool {
return true
}

View File

@@ -195,11 +195,11 @@ func (logic *Logic) RuleType() C.RuleType {
return logic.ruleType return logic.ruleType
} }
func matchSubRules(metadata *C.Metadata, name string, subRules map[string][]C.Rule) (bool, string) { func matchSubRules(metadata *C.Metadata, name string, subRules map[string][]C.Rule, helper C.RuleMatchHelper) (bool, string) {
for _, rule := range subRules[name] { for _, rule := range subRules[name] {
if m, a := rule.Match(metadata); m { if m, a := rule.Match(metadata, helper); m {
if rule.RuleType() == C.SubRules { if rule.RuleType() == C.SubRules {
return matchSubRules(metadata, rule.Adapter(), subRules) return matchSubRules(metadata, rule.Adapter(), subRules, helper)
} else { } else {
return m, a return m, a
} }
@@ -208,28 +208,28 @@ func matchSubRules(metadata *C.Metadata, name string, subRules map[string][]C.Ru
return false, "" return false, ""
} }
func (logic *Logic) Match(metadata *C.Metadata) (bool, string) { func (logic *Logic) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
switch logic.ruleType { switch logic.ruleType {
case C.SubRules: case C.SubRules:
if m, _ := logic.rules[0].Match(metadata); m { if m, _ := logic.rules[0].Match(metadata, helper); m {
return matchSubRules(metadata, logic.adapter, logic.subRules) return matchSubRules(metadata, logic.adapter, logic.subRules, helper)
} }
return false, "" return false, ""
case C.NOT: case C.NOT:
if m, _ := logic.rules[0].Match(metadata); !m { if m, _ := logic.rules[0].Match(metadata, helper); !m {
return true, logic.adapter return true, logic.adapter
} }
return false, "" return false, ""
case C.OR: case C.OR:
for _, rule := range logic.rules { for _, rule := range logic.rules {
if m, _ := rule.Match(metadata); m { if m, _ := rule.Match(metadata, helper); m {
return true, logic.adapter return true, logic.adapter
} }
} }
return false, "" return false, ""
case C.AND: case C.AND:
for _, rule := range logic.rules { for _, rule := range logic.rules {
if m, _ := rule.Match(metadata); !m { if m, _ := rule.Match(metadata, helper); !m {
return false, logic.adapter return false, logic.adapter
} }
} }
@@ -266,38 +266,6 @@ func (logic *Logic) Payload() string {
return logic.payload return logic.payload
} }
func (logic *Logic) ShouldResolveIP() bool {
if logic.ruleType == C.SubRules {
for _, rule := range logic.subRules[logic.adapter] {
if rule.ShouldResolveIP() {
return true
}
}
}
for _, rule := range logic.rules {
if rule.ShouldResolveIP() {
return true
}
}
return false
}
func (logic *Logic) ShouldFindProcess() bool {
if logic.ruleType == C.SubRules {
for _, rule := range logic.subRules[logic.adapter] {
if rule.ShouldFindProcess() {
return true
}
}
}
for _, rule := range logic.rules {
if rule.ShouldFindProcess() {
return true
}
}
return false
}
func (logic *Logic) ProviderNames() (names []string) { func (logic *Logic) ProviderNames() (names []string) {
for _, rule := range logic.rules { for _, rule := range logic.rules {
names = append(names, rule.ProviderNames()...) names = append(names, rule.ProviderNames()...)

View File

@@ -16,12 +16,11 @@ func TestAND(t *testing.T) {
and, err := NewAND("((DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT", ParseRule) and, err := NewAND("((DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT", ParseRule)
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.Equal(t, "DIRECT", and.Adapter()) assert.Equal(t, "DIRECT", and.Adapter())
assert.Equal(t, false, and.ShouldResolveIP())
m, _ := and.Match(&C.Metadata{ m, _ := and.Match(&C.Metadata{
Host: "baidu.com", Host: "baidu.com",
NetWork: C.TCP, NetWork: C.TCP,
DstPort: 20000, DstPort: 20000,
}) }, C.RuleMatchHelper{})
assert.Equal(t, true, m) assert.Equal(t, true, m)
and, err = NewAND("(DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT", ParseRule) and, err = NewAND("(DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT", ParseRule)
@@ -36,7 +35,7 @@ func TestNOT(t *testing.T) {
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
m, _ := not.Match(&C.Metadata{ m, _ := not.Match(&C.Metadata{
DstPort: 6100, DstPort: 6100,
}) }, C.RuleMatchHelper{})
assert.Equal(t, false, m) assert.Equal(t, false, m)
_, err = NewNOT("((DST-PORT,5600-6666),(DOMAIN,baidu.com))", "DIRECT", ParseRule) _, err = NewNOT("((DST-PORT,5600-6666),(DOMAIN,baidu.com))", "DIRECT", ParseRule)
@@ -51,7 +50,6 @@ func TestOR(t *testing.T) {
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
m, _ := or.Match(&C.Metadata{ m, _ := or.Match(&C.Metadata{
NetWork: C.TCP, NetWork: C.TCP,
}) }, C.RuleMatchHelper{})
assert.Equal(t, true, m) assert.Equal(t, true, m)
assert.Equal(t, false, or.ShouldResolveIP())
} }

View File

@@ -10,20 +10,18 @@ import (
) )
type classicalStrategy struct { type classicalStrategy struct {
rules []C.Rule rules []C.Rule
count int count int
shouldResolveIP bool parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)
shouldFindProcess bool
parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)
} }
func (c *classicalStrategy) Behavior() P.RuleBehavior { func (c *classicalStrategy) Behavior() P.RuleBehavior {
return P.Classical return P.Classical
} }
func (c *classicalStrategy) Match(metadata *C.Metadata) bool { func (c *classicalStrategy) Match(metadata *C.Metadata, helper C.RuleMatchHelper) bool {
for _, rule := range c.rules { for _, rule := range c.rules {
if m, _ := rule.Match(metadata); m { if m, _ := rule.Match(metadata, helper); m {
return true return true
} }
} }
@@ -35,39 +33,17 @@ func (c *classicalStrategy) Count() int {
return c.count return c.count
} }
func (c *classicalStrategy) ShouldResolveIP() bool {
return c.shouldResolveIP
}
func (c *classicalStrategy) ShouldFindProcess() bool {
return c.shouldFindProcess
}
func (c *classicalStrategy) Reset() { func (c *classicalStrategy) Reset() {
c.rules = nil c.rules = nil
c.count = 0 c.count = 0
c.shouldFindProcess = false
c.shouldResolveIP = false
} }
func (c *classicalStrategy) Insert(rule string) { func (c *classicalStrategy) Insert(rule string) {
ruleType, rule, params := ruleParse(rule) ruleType, rule, params := ruleParse(rule)
if ruleType == "PROCESS-NAME" {
c.shouldFindProcess = true
}
r, err := c.parse(ruleType, rule, "", params) r, err := c.parse(ruleType, rule, "", params)
if err != nil { if err != nil {
log.Warnln("parse classical rule error: %s", err.Error()) log.Warnln("parse classical rule error: %s", err.Error())
} else { } else {
if r.ShouldResolveIP() {
c.shouldResolveIP = true
}
if r.ShouldFindProcess() {
c.shouldFindProcess = true
}
c.rules = append(c.rules, r) c.rules = append(c.rules, r)
c.count++ c.count++
} }

View File

@@ -23,11 +23,7 @@ func (d *domainStrategy) Behavior() P.RuleBehavior {
return P.Domain return P.Domain
} }
func (d *domainStrategy) ShouldFindProcess() bool { func (d *domainStrategy) Match(metadata *C.Metadata, helper C.RuleMatchHelper) bool {
return false
}
func (d *domainStrategy) Match(metadata *C.Metadata) bool {
return d.domainSet != nil && d.domainSet.Has(metadata.RuleHost()) return d.domainSet != nil && d.domainSet.Has(metadata.RuleHost())
} }
@@ -35,10 +31,6 @@ func (d *domainStrategy) Count() int {
return d.count return d.count
} }
func (d *domainStrategy) ShouldResolveIP() bool {
return false
}
func (d *domainStrategy) Reset() { func (d *domainStrategy) Reset() {
d.domainTrie = trie.New[struct{}]() d.domainTrie = trie.New[struct{}]()
d.domainSet = nil d.domainSet = nil

View File

@@ -14,21 +14,19 @@ import (
) )
type ipcidrStrategy struct { type ipcidrStrategy struct {
count int count int
shouldResolveIP bool cidrSet *cidr.IpCidrSet
cidrSet *cidr.IpCidrSet //trie *trie.IpCidrTrie
//trie *trie.IpCidrTrie
} }
func (i *ipcidrStrategy) Behavior() P.RuleBehavior { func (i *ipcidrStrategy) Behavior() P.RuleBehavior {
return P.IPCIDR return P.IPCIDR
} }
func (i *ipcidrStrategy) ShouldFindProcess() bool { func (i *ipcidrStrategy) Match(metadata *C.Metadata, helper C.RuleMatchHelper) bool {
return false if helper.ResolveIP != nil {
} helper.ResolveIP()
}
func (i *ipcidrStrategy) Match(metadata *C.Metadata) bool {
// return i.trie != nil && i.trie.IsContain(metadata.DstIP.AsSlice()) // return i.trie != nil && i.trie.IsContain(metadata.DstIP.AsSlice())
return i.cidrSet != nil && i.cidrSet.IsContain(metadata.DstIP) return i.cidrSet != nil && i.cidrSet.IsContain(metadata.DstIP)
} }
@@ -37,15 +35,10 @@ func (i *ipcidrStrategy) Count() int {
return i.count return i.count
} }
func (i *ipcidrStrategy) ShouldResolveIP() bool {
return i.shouldResolveIP
}
func (i *ipcidrStrategy) Reset() { func (i *ipcidrStrategy) Reset() {
// i.trie = trie.NewIpCidrTrie() // i.trie = trie.NewIpCidrTrie()
i.cidrSet = cidr.NewIpCidrSet() i.cidrSet = cidr.NewIpCidrSet()
i.count = 0 i.count = 0
i.shouldResolveIP = false
} }
func (i *ipcidrStrategy) Insert(rule string) { func (i *ipcidrStrategy) Insert(rule string) {
@@ -54,7 +47,6 @@ func (i *ipcidrStrategy) Insert(rule string) {
if err != nil { if err != nil {
log.Warnln("invalid Ipcidr:[%s]", rule) log.Warnln("invalid Ipcidr:[%s]", rule)
} else { } else {
i.shouldResolveIP = true
i.count++ i.count++
} }
} }
@@ -70,9 +62,6 @@ func (i *ipcidrStrategy) FromMrs(r io.Reader, count int) error {
} }
i.count = count i.count = count
i.cidrSet = cidrSet i.cidrSet = cidrSet
if i.count > 0 {
i.shouldResolveIP = true
}
return nil return nil
} }

View File

@@ -46,10 +46,8 @@ type providerForApi struct {
type ruleStrategy interface { type ruleStrategy interface {
Behavior() P.RuleBehavior Behavior() P.RuleBehavior
Match(metadata *C.Metadata) bool Match(metadata *C.Metadata, helper C.RuleMatchHelper) bool
Count() int Count() int
ShouldResolveIP() bool
ShouldFindProcess() bool
Reset() Reset()
Insert(rule string) Insert(rule string)
FinishInsert() FinishInsert()
@@ -79,16 +77,8 @@ func (bp *baseProvider) Count() int {
return bp.strategy.Count() return bp.strategy.Count()
} }
func (bp *baseProvider) Match(metadata *C.Metadata) bool { func (bp *baseProvider) Match(metadata *C.Metadata, helper C.RuleMatchHelper) bool {
return bp.strategy != nil && bp.strategy.Match(metadata) return bp.strategy != nil && bp.strategy.Match(metadata, helper)
}
func (bp *baseProvider) ShouldResolveIP() bool {
return bp.strategy.ShouldResolveIP()
}
func (bp *baseProvider) ShouldFindProcess() bool {
return bp.strategy.ShouldFindProcess()
} }
func (bp *baseProvider) Strategy() any { func (bp *baseProvider) Strategy() any {

View File

@@ -10,47 +10,40 @@ import (
type RuleSet struct { type RuleSet struct {
*common.Base *common.Base
ruleProviderName string ruleProviderName string
adapter string adapter string
isSrc bool isSrc bool
noResolveIP bool noResolveIP bool
shouldFindProcess bool
}
func (rs *RuleSet) ShouldFindProcess() bool {
if rs.shouldFindProcess {
return true
}
if provider, ok := rs.getProvider(); ok {
return provider.ShouldFindProcess()
}
return false
} }
func (rs *RuleSet) RuleType() C.RuleType { func (rs *RuleSet) RuleType() C.RuleType {
return C.RuleSet return C.RuleSet
} }
func (rs *RuleSet) Match(metadata *C.Metadata) (bool, string) { func (rs *RuleSet) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
if provider, ok := rs.getProvider(); ok { if provider, ok := rs.getProvider(); ok {
if rs.isSrc { if rs.isSrc {
metadata.SwapSrcDst() metadata.SwapSrcDst()
defer metadata.SwapSrcDst() defer metadata.SwapSrcDst()
helper.ResolveIP = nil // src mode should not resolve ip
} else if rs.noResolveIP {
helper.ResolveIP = nil
} }
return provider.Match(metadata), rs.adapter return provider.Match(metadata, helper), rs.adapter
} }
return false, "" return false, ""
} }
// MatchDomain implements C.DomainMatcher // MatchDomain implements C.DomainMatcher
func (rs *RuleSet) MatchDomain(domain string) bool { func (rs *RuleSet) MatchDomain(domain string) bool {
ok, _ := rs.Match(&C.Metadata{Host: domain}) ok, _ := rs.Match(&C.Metadata{Host: domain}, C.RuleMatchHelper{})
return ok return ok
} }
// MatchIp implements C.IpMatcher // MatchIp implements C.IpMatcher
func (rs *RuleSet) MatchIp(ip netip.Addr) bool { func (rs *RuleSet) MatchIp(ip netip.Addr) bool {
ok, _ := rs.Match(&C.Metadata{DstIP: ip}) ok, _ := rs.Match(&C.Metadata{DstIP: ip}, C.RuleMatchHelper{})
return ok return ok
} }
@@ -62,16 +55,6 @@ func (rs *RuleSet) Payload() string {
return rs.ruleProviderName return rs.ruleProviderName
} }
func (rs *RuleSet) ShouldResolveIP() bool {
if rs.noResolveIP {
return false
}
if provider, ok := rs.getProvider(); ok {
return provider.ShouldResolveIP()
}
return false
}
func (rs *RuleSet) ProviderNames() []string { func (rs *RuleSet) ProviderNames() []string {
return []string{rs.ruleProviderName} return []string{rs.ruleProviderName}
} }

View File

@@ -0,0 +1,55 @@
package shadowstream
import (
"crypto/cipher"
"github.com/metacubex/chacha"
)
func newChaCha20(nonce, key []byte) cipher.Stream {
c, err := chacha.NewChaCha20IgnoreCounterOverflow(nonce, key)
if err != nil {
panic(err) // should never happen
}
return c
}
type chacha20key []byte
func (k chacha20key) IVSize() int { return chacha.NonceSize }
func (k chacha20key) Encrypter(iv []byte) cipher.Stream { return newChaCha20(iv, k) }
func (k chacha20key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
func ChaCha20(key []byte) (Cipher, error) {
if len(key) != chacha.KeySize {
return nil, KeySizeError(chacha.KeySize)
}
return chacha20key(key), nil
}
// IETF-variant of chacha20
type chacha20ietfkey []byte
func (k chacha20ietfkey) IVSize() int { return chacha.INonceSize }
func (k chacha20ietfkey) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
func (k chacha20ietfkey) Encrypter(iv []byte) cipher.Stream { return newChaCha20(iv, k) }
func Chacha20IETF(key []byte) (Cipher, error) {
if len(key) != chacha.KeySize {
return nil, KeySizeError(chacha.KeySize)
}
return chacha20ietfkey(key), nil
}
type xchacha20key []byte
func (k xchacha20key) IVSize() int { return chacha.XNonceSize }
func (k xchacha20key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
func (k xchacha20key) Encrypter(iv []byte) cipher.Stream { return newChaCha20(iv, k) }
func Xchacha20(key []byte) (Cipher, error) {
if len(key) != chacha.KeySize {
return nil, KeySizeError(chacha.KeySize)
}
return xchacha20key(key), nil
}

View File

@@ -6,8 +6,6 @@ import (
"crypto/md5" "crypto/md5"
"crypto/rc4" "crypto/rc4"
"strconv" "strconv"
"golang.org/x/crypto/chacha20"
) )
// Cipher generates a pair of stream ciphers for encryption and decryption. // Cipher generates a pair of stream ciphers for encryption and decryption.
@@ -53,45 +51,6 @@ func AESCFB(key []byte) (Cipher, error) {
return &cfbStream{blk}, nil return &cfbStream{blk}, nil
} }
// IETF-variant of chacha20
type chacha20ietfkey []byte
func (k chacha20ietfkey) IVSize() int { return chacha20.NonceSize }
func (k chacha20ietfkey) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
func (k chacha20ietfkey) Encrypter(iv []byte) cipher.Stream {
ciph, err := chacha20.NewUnauthenticatedCipher(k, iv)
if err != nil {
panic(err) // should never happen
}
return ciph
}
func Chacha20IETF(key []byte) (Cipher, error) {
if len(key) != chacha20.KeySize {
return nil, KeySizeError(chacha20.KeySize)
}
return chacha20ietfkey(key), nil
}
type xchacha20key []byte
func (k xchacha20key) IVSize() int { return chacha20.NonceSizeX }
func (k xchacha20key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
func (k xchacha20key) Encrypter(iv []byte) cipher.Stream {
ciph, err := chacha20.NewUnauthenticatedCipher(k, iv)
if err != nil {
panic(err) // should never happen
}
return ciph
}
func Xchacha20(key []byte) (Cipher, error) {
if len(key) != chacha20.KeySize {
return nil, KeySizeError(chacha20.KeySize)
}
return xchacha20key(key), nil
}
type rc4Md5Key []byte type rc4Md5Key []byte
func (k rc4Md5Key) IVSize() int { func (k rc4Md5Key) IVSize() int {

View File

@@ -1,23 +0,0 @@
package shadowstream
import (
"crypto/cipher"
"github.com/metacubex/chacha"
)
type chacha20key []byte
func (k chacha20key) IVSize() int {
return chacha.NonceSize
}
func (k chacha20key) Encrypter(iv []byte) cipher.Stream {
c, _ := chacha.NewChaCha20(iv, k)
return c
}
func (k chacha20key) Decrypter(iv []byte) cipher.Stream {
return k.Encrypter(iv)
}
func ChaCha20(key []byte) (Cipher, error) {
return chacha20key(key), nil
}

View File

@@ -118,6 +118,10 @@ func ServerHandshake(rw net.Conn, authenticator auth.Authenticator) (addr Addr,
return return
} }
if nmethods == 1 && buf[0] == 0x02 /* will use password */ && authenticator == nil {
authenticator = auth.AlwaysValid
}
// write VER METHOD // write VER METHOD
if authenticator != nil { if authenticator != nil {
if _, err = rw.Write([]byte{5, 2}); err != nil { if _, err = rw.Write([]byte{5, 2}); err != nil {

View File

@@ -129,7 +129,7 @@ func (c *httpConn) Write(b []byte) (int, error) {
buf.Write(b) buf.Write(b)
_, err := c.Conn.Write(buf.Bytes()) _, err := c.Conn.Write(buf.Bytes())
if err != nil { if err != nil {
return 0, nil return 0, err
} }
c.hasSentHeader = true c.hasSentHeader = true
return bLength, nil return bLength, nil

View File

@@ -6,6 +6,7 @@ import (
"errors" "errors"
"io" "io"
"net" "net"
"time"
) )
type SessionStatus = byte type SessionStatus = byte
@@ -37,7 +38,6 @@ type Mux struct {
id [2]byte id [2]byte
length [2]byte length [2]byte
status [2]byte status [2]byte
otb []byte
remain int remain int
} }
@@ -104,14 +104,8 @@ func (m *Mux) Read(b []byte) (int, error) {
} }
func (m *Mux) Write(b []byte) (int, error) { func (m *Mux) Write(b []byte) (int, error) {
if m.otb != nil { defer m.buf.Reset() // reset must after write (keep the data fill in NewMux can be sent)
// create a sub connection
if _, err := m.Conn.Write(m.otb); err != nil {
return 0, err
}
m.otb = nil
}
m.buf.Reset()
binary.Write(&m.buf, binary.BigEndian, uint16(4)) binary.Write(&m.buf, binary.BigEndian, uint16(4))
m.buf.Write(m.id[:]) m.buf.Write(m.id[:])
m.buf.WriteByte(SessionStatusKeep) m.buf.WriteByte(SessionStatusKeep)
@@ -123,15 +117,26 @@ func (m *Mux) Write(b []byte) (int, error) {
} }
func (m *Mux) Close() error { func (m *Mux) Close() error {
_, err := m.Conn.Write([]byte{0x0, 0x4, m.id[0], m.id[1], SessionStatusEnd, OptionNone}) errChan := make(chan error, 1)
if err != nil { t := time.AfterFunc(time.Second, func() { // maybe conn write too slowly, force close underlay conn after one second
return err errChan <- m.Conn.Close()
})
_, _ = m.Conn.Write([]byte{0x0, 0x4, m.id[0], m.id[1], SessionStatusEnd, OptionNone}) // ignore session end frame write error
if !t.Stop() {
// Stop does not wait for f to complete before returning, so we used a chan to know whether f is completed
return <-errChan
} }
return m.Conn.Close() return m.Conn.Close()
} }
func NewMux(conn net.Conn, option MuxOption) *Mux { func NewMux(conn net.Conn, option MuxOption) *Mux {
buf := &bytes.Buffer{} mux := &Mux{
Conn: conn,
id: option.ID,
}
// create a sub connection (in buf)
buf := &mux.buf
// fill empty length // fill empty length
buf.Write([]byte{0x0, 0x0}) buf.Write([]byte{0x0, 0x0})
@@ -165,9 +170,5 @@ func NewMux(conn net.Conn, option MuxOption) *Mux {
metadata := buf.Bytes() metadata := buf.Bytes()
binary.BigEndian.PutUint16(metadata[:2], uint16(len(metadata)-2)) binary.BigEndian.PutUint16(metadata[:2], uint16(len(metadata)-2))
return &Mux{ return mux
Conn: conn,
id: option.ID,
otb: metadata,
}
} }

View File

@@ -1,12 +1,11 @@
package tunnel package tunnel
import ( import (
"encoding/json"
"errors" "errors"
"strings" "strings"
) )
type TunnelMode int type TunnelMode int32
// ModeMapping is a mapping for Mode enum // ModeMapping is a mapping for Mode enum
var ModeMapping = map[string]TunnelMode{ var ModeMapping = map[string]TunnelMode{
@@ -21,30 +20,6 @@ const (
Direct Direct
) )
// UnmarshalYAML unserialize Mode with yaml
func (m *TunnelMode) UnmarshalYAML(unmarshal func(any) error) error {
var tp string
unmarshal(&tp)
mode, exist := ModeMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid mode")
}
*m = mode
return nil
}
// UnmarshalJSON unserialize Mode
func (m *TunnelMode) UnmarshalJSON(data []byte) error {
var tp string
json.Unmarshal(data, &tp)
mode, exist := ModeMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid mode")
}
*m = mode
return nil
}
// UnmarshalText unserialize Mode // UnmarshalText unserialize Mode
func (m *TunnelMode) UnmarshalText(data []byte) error { func (m *TunnelMode) UnmarshalText(data []byte) error {
mode, exist := ModeMapping[strings.ToLower(string(data))] mode, exist := ModeMapping[strings.ToLower(string(data))]
@@ -55,16 +30,6 @@ func (m *TunnelMode) UnmarshalText(data []byte) error {
return nil return nil
} }
// MarshalYAML serialize TunnelMode with yaml
func (m TunnelMode) MarshalYAML() (any, error) {
return m.String(), nil
}
// MarshalJSON serialize Mode
func (m TunnelMode) MarshalJSON() ([]byte, error) {
return json.Marshal(m.String())
}
// MarshalText serialize Mode // MarshalText serialize Mode
func (m TunnelMode) MarshalText() ([]byte, error) { func (m TunnelMode) MarshalText() ([]byte, error) {
return []byte(m.String()), nil return []byte(m.String()), nil

View File

@@ -1,13 +1,11 @@
package tunnel package tunnel
import ( import (
"encoding/json"
"errors" "errors"
"strings" "strings"
"sync/atomic"
) )
type TunnelStatus int type TunnelStatus int32
// StatusMapping is a mapping for Status enum // StatusMapping is a mapping for Status enum
var StatusMapping = map[string]TunnelStatus{ var StatusMapping = map[string]TunnelStatus{
@@ -22,30 +20,6 @@ const (
Running Running
) )
// UnmarshalYAML unserialize Status with yaml
func (s *TunnelStatus) UnmarshalYAML(unmarshal func(any) error) error {
var tp string
unmarshal(&tp)
status, exist := StatusMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid status")
}
*s = status
return nil
}
// UnmarshalJSON unserialize Status
func (s *TunnelStatus) UnmarshalJSON(data []byte) error {
var tp string
json.Unmarshal(data, &tp)
status, exist := StatusMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid status")
}
*s = status
return nil
}
// UnmarshalText unserialize Status // UnmarshalText unserialize Status
func (s *TunnelStatus) UnmarshalText(data []byte) error { func (s *TunnelStatus) UnmarshalText(data []byte) error {
status, exist := StatusMapping[strings.ToLower(string(data))] status, exist := StatusMapping[strings.ToLower(string(data))]
@@ -56,16 +30,6 @@ func (s *TunnelStatus) UnmarshalText(data []byte) error {
return nil return nil
} }
// MarshalYAML serialize TunnelMode with yaml
func (s TunnelStatus) MarshalYAML() (any, error) {
return s.String(), nil
}
// MarshalJSON serialize Status
func (s TunnelStatus) MarshalJSON() ([]byte, error) {
return json.Marshal(s.String())
}
// MarshalText serialize Status // MarshalText serialize Status
func (s TunnelStatus) MarshalText() ([]byte, error) { func (s TunnelStatus) MarshalText() ([]byte, error) {
return []byte(s.String()), nil return []byte(s.String()), nil
@@ -83,25 +47,3 @@ func (s TunnelStatus) String() string {
return "Unknown" return "Unknown"
} }
} }
type AtomicStatus struct {
value atomic.Int32
}
func (a *AtomicStatus) Store(s TunnelStatus) {
a.value.Store(int32(s))
}
func (a *AtomicStatus) Load() TunnelStatus {
return TunnelStatus(a.value.Load())
}
func (a *AtomicStatus) String() string {
return a.Load().String()
}
func newAtomicStatus(s TunnelStatus) *AtomicStatus {
a := &AtomicStatus{}
a.Store(s)
return a
}

View File

@@ -12,6 +12,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/metacubex/mihomo/common/atomic"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/loopback" "github.com/metacubex/mihomo/component/loopback"
@@ -34,7 +35,7 @@ const (
) )
var ( var (
status = newAtomicStatus(Suspend) status = atomic.NewInt32Enum(Suspend)
udpInit sync.Once udpInit sync.Once
udpQueues []chan C.PacketAdapter udpQueues []chan C.PacketAdapter
natTable = nat.New() natTable = nat.New()
@@ -58,7 +59,7 @@ var (
// default timeout for UDP session // default timeout for UDP session
udpTimeout = 60 * time.Second udpTimeout = 60 * time.Second
findProcessMode P.FindProcessMode findProcessMode = atomic.NewInt32Enum(P.FindProcessStrict)
fakeIPRange netip.Prefix fakeIPRange netip.Prefix
@@ -273,13 +274,13 @@ func SetMode(m TunnelMode) {
} }
func FindProcessMode() P.FindProcessMode { func FindProcessMode() P.FindProcessMode {
return findProcessMode return findProcessMode.Load()
} }
// SetFindProcessMode replace SetAlwaysFindProcess // SetFindProcessMode replace SetAlwaysFindProcess
// always find process info if legacyAlways = true or mode.Always() = true, may be increase many memory // always find process info if legacyAlways = true or mode.Always() = true, may be increase many memory
func SetFindProcessMode(mode P.FindProcessMode) { func SetFindProcessMode(mode P.FindProcessMode) {
findProcessMode = mode findProcessMode.Store(mode)
} }
func isHandle(t C.Type) bool { func isHandle(t C.Type) bool {
@@ -337,6 +338,68 @@ func resolveMetadata(metadata *C.Metadata) (proxy C.Proxy, rule C.Rule, err erro
} }
return return
} }
var (
resolved bool
attemptProcessLookup = metadata.Type != C.INNER
)
if node, ok := resolver.DefaultHosts.Search(metadata.Host, false); ok {
metadata.DstIP, _ = node.RandIP()
resolved = true
}
helper := C.RuleMatchHelper{
ResolveIP: func() {
if !resolved && metadata.Host != "" && !metadata.Resolved() {
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
defer cancel()
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
log.Debugln("[DNS] resolve %s error: %s", metadata.Host, err.Error())
} else {
log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String())
metadata.DstIP = ip
}
resolved = true
}
},
FindProcess: func() {
if attemptProcessLookup {
attemptProcessLookup = false
if !features.CMFA {
// normal check for process
uid, path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, int(metadata.SrcPort))
if err != nil {
log.Debugln("[Process] find process error for %s: %v", metadata.String(), err)
} else {
metadata.Process = filepath.Base(path)
metadata.ProcessPath = path
metadata.Uid = uid
if pkg, err := P.FindPackageName(metadata); err == nil { // for android (not CMFA) package names
metadata.Process = pkg
}
}
} else {
// check package names
pkg, err := P.FindPackageName(metadata)
if err != nil {
log.Debugln("[Process] find process error for %s: %v", metadata.String(), err)
} else {
metadata.Process = pkg
}
}
}
},
}
switch FindProcessMode() {
case P.FindProcessAlways:
helper.FindProcess()
helper.FindProcess = nil
case P.FindProcessOff:
helper.FindProcess = nil
}
switch mode { switch mode {
case Direct: case Direct:
@@ -345,7 +408,7 @@ func resolveMetadata(metadata *C.Metadata) (proxy C.Proxy, rule C.Rule, err erro
proxy = proxies["GLOBAL"] proxy = proxies["GLOBAL"]
// Rule // Rule
default: default:
proxy, rule, err = match(metadata) proxy, rule, err = match(metadata, helper)
} }
return return
} }
@@ -590,67 +653,12 @@ func logMetadata(metadata *C.Metadata, rule C.Rule, remoteConn C.Connection) {
} }
} }
func shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool { func match(metadata *C.Metadata, helper C.RuleMatchHelper) (C.Proxy, C.Rule, error) {
return rule.ShouldResolveIP() && metadata.Host != "" && !metadata.DstIP.IsValid()
}
func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
configMux.RLock() configMux.RLock()
defer configMux.RUnlock() defer configMux.RUnlock()
var (
resolved bool
attemptProcessLookup = metadata.Type != C.INNER
)
if node, ok := resolver.DefaultHosts.Search(metadata.Host, false); ok {
metadata.DstIP, _ = node.RandIP()
resolved = true
}
for _, rule := range getRules(metadata) { for _, rule := range getRules(metadata) {
if !resolved && shouldResolveIP(rule, metadata) { if matched, ada := rule.Match(metadata, helper); matched {
func() {
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
defer cancel()
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
log.Debugln("[DNS] resolve %s error: %s", metadata.Host, err.Error())
} else {
log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String())
metadata.DstIP = ip
}
resolved = true
}()
}
if attemptProcessLookup && !findProcessMode.Off() && (findProcessMode.Always() || rule.ShouldFindProcess()) {
attemptProcessLookup = false
if !features.CMFA {
// normal check for process
uid, path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, int(metadata.SrcPort))
if err != nil {
log.Debugln("[Process] find process error for %s: %v", metadata.String(), err)
} else {
metadata.Process = filepath.Base(path)
metadata.ProcessPath = path
metadata.Uid = uid
if pkg, err := P.FindPackageName(metadata); err == nil { // for android (not CMFA) package names
metadata.Process = pkg
}
}
} else {
// check package names
pkg, err := P.FindPackageName(metadata)
if err != nil {
log.Debugln("[Process] find process error for %s: %v", metadata.String(), err)
} else {
metadata.Process = pkg
}
}
}
if matched, ada := rule.Match(metadata); matched {
adapter, ok := proxies[ada] adapter, ok := proxies[ada]
if !ok { if !ok {
continue continue