diff --git a/common/structure/structure_test.go b/common/structure/structure_test.go index 266b828f..fcdd853a 100644 --- a/common/structure/structure_test.go +++ b/common/structure/structure_test.go @@ -239,13 +239,15 @@ func (n *num) UnmarshalText(text []byte) (err error) { func TestStructure_TextUnmarshaller(t *testing.T) { rawMap := map[string]any{ - "num": "255", - "num_p": "127", + "num": "255", + "num_p": "127", + "num_arr": []string{"1", "2", "3"}, } s := &struct { - Num num `test:"num"` - NumP *num `test:"num_p"` + Num num `test:"num"` + NumP *num `test:"num_p"` + NumArr []num `test:"num_arr"` }{} err := decoder.Decode(rawMap, s) @@ -253,6 +255,7 @@ func TestStructure_TextUnmarshaller(t *testing.T) { assert.Equal(t, 255, s.Num.a) assert.NotNil(t, s.NumP) assert.Equal(t, s.NumP.a, 127) + assert.Equal(t, s.NumArr, []num{{1}, {2}, {3}}) // test WeaklyTypedInput rawMap["num"] = 256 diff --git a/config/config.go b/config/config.go index 498c7c20..3faca185 100644 --- a/config/config.go +++ b/config/config.go @@ -270,6 +270,7 @@ type RawTun struct { AutoRedirect bool `yaml:"auto-redirect" json:"auto-redirect,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"` + LoopbackAddress []netip.Addr `yaml:"loopback-address" json:"loopback-address,omitempty"` StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"` RouteAddress []netip.Prefix `yaml:"route-address" json:"route-address,omitempty"` RouteAddressSet []string `yaml:"route-address-set" json:"route-address-set,omitempty"` @@ -1559,6 +1560,7 @@ func parseTun(rawTun RawTun, general *General) error { AutoRedirect: rawTun.AutoRedirect, AutoRedirectInputMark: rawTun.AutoRedirectInputMark, AutoRedirectOutputMark: rawTun.AutoRedirectOutputMark, + LoopbackAddress: rawTun.LoopbackAddress, StrictRoute: rawTun.StrictRoute, RouteAddress: rawTun.RouteAddress, RouteAddressSet: rawTun.RouteAddressSet, diff --git a/go.mod b/go.mod index 21ae1114..d1da5c36 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/metacubex/sing-shadowsocks v0.2.11-0.20250531133822-e545de386d4c github.com/metacubex/sing-shadowsocks2 v0.2.5-0.20250531133559-f4d53bd59335 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-wireguard v0.0.0-20250503063753-2dc62acc626f github.com/metacubex/smux v0.0.0-20250503055512-501391591dee diff --git a/go.sum b/go.sum index e93e1c70..9c0c63d5 100644 --- a/go.sum +++ b/go.sum @@ -128,8 +128,8 @@ github.com/metacubex/sing-shadowsocks2 v0.2.5-0.20250531133559-f4d53bd59335 h1:n github.com/metacubex/sing-shadowsocks2 v0.2.5-0.20250531133559-f4d53bd59335/go.mod h1:WP8+S0kqtnSbX1vlIpo5i8Irm/ijZITEPBcZ26B5unY= 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-tun v0.4.6-0.20250524142129-9d110c0af70c h1:Y6jk7AH5BEg9Dsvczrf/KokYsvxeKSZZlCLHg+hC4ro= -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 h1:4zWKqxTx75TbfW2EmlQ3hxM6RTRg2PYOAVMCnU4I61I= +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/go.mod h1:CVDNcdSLVYFgTHQlubr88d8CdqupAUDqLjROos+H9xk= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU= diff --git a/hub/route/configs.go b/hub/route/configs.go index c387c949..2ccd5af1 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -75,6 +75,7 @@ type tunSchema struct { AutoRedirect *bool `yaml:"auto-redirect" json:"auto-redirect,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"` + LoopbackAddress *[]netip.Addr `yaml:"loopback-address" json:"loopback-address,omitempty"` StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"` RouteAddress *[]netip.Prefix `yaml:"route-address" json:"route-address,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 { def.AutoRedirectOutputMark = *p.AutoRedirectOutputMark } + if p.LoopbackAddress != nil { + def.LoopbackAddress = *p.LoopbackAddress + } if p.StrictRoute != nil { def.StrictRoute = *p.StrictRoute } diff --git a/listener/config/tun.go b/listener/config/tun.go index 4e12056d..fa20db1e 100644 --- a/listener/config/tun.go +++ b/listener/config/tun.go @@ -9,18 +9,6 @@ import ( "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 { Enable bool `yaml:"enable" json:"enable"` Device string `yaml:"device" json:"device"` @@ -39,6 +27,7 @@ type Tun struct { AutoRedirect bool `yaml:"auto-redirect" json:"auto-redirect,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"` + LoopbackAddress []netip.Addr `yaml:"loopback-address" json:"loopback-address,omitempty"` StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"` RouteAddress []netip.Prefix `yaml:"route-address" json:"route-address,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 { return false } + if !slices.Equal(t.RouteAddress, other.RouteAddress) { + return false + } if t.StrictRoute != other.StrictRoute { return false } diff --git a/listener/inbound/tun.go b/listener/inbound/tun.go index 270bf3fa..11cad0c6 100644 --- a/listener/inbound/tun.go +++ b/listener/inbound/tun.go @@ -1,8 +1,8 @@ package inbound import ( - "errors" - "strings" + "encoding" + "net/netip" C "github.com/metacubex/mihomo/constant" LC "github.com/metacubex/mihomo/listener/config" @@ -12,50 +12,55 @@ import ( type TunOption struct { BaseOption - Device string `inbound:"device,omitempty"` - Stack string `inbound:"stack,omitempty"` - DNSHijack []string `inbound:"dns-hijack,omitempty"` - AutoRoute bool `inbound:"auto-route,omitempty"` - AutoDetectInterface bool `inbound:"auto-detect-interface,omitempty"` + Device string `inbound:"device,omitempty"` + Stack C.TUNStack `inbound:"stack,omitempty"` + DNSHijack []string `inbound:"dns-hijack,omitempty"` + AutoRoute bool `inbound:"auto-route,omitempty"` + AutoDetectInterface bool `inbound:"auto-detect-interface,omitempty"` - MTU uint32 `inbound:"mtu,omitempty"` - GSO bool `inbound:"gso,omitempty"` - GSOMaxSize uint32 `inbound:"gso-max-size,omitempty"` - Inet4Address []string `inbound:"inet4-address,omitempty"` - Inet6Address []string `inbound:"inet6-address,omitempty"` - IPRoute2TableIndex int `inbound:"iproute2-table-index,omitempty"` - IPRoute2RuleIndex int `inbound:"iproute2-rule-index,omitempty"` - AutoRedirect bool `inbound:"auto-redirect,omitempty"` - AutoRedirectInputMark uint32 `inbound:"auto-redirect-input-mark,omitempty"` - AutoRedirectOutputMark uint32 `inbound:"auto-redirect-output-mark,omitempty"` - StrictRoute bool `inbound:"strict-route,omitempty"` - RouteAddress []string `inbound:"route-address,omitempty"` - RouteAddressSet []string `inbound:"route-address-set,omitempty"` - RouteExcludeAddress []string `inbound:"route-exclude-address,omitempty"` - RouteExcludeAddressSet []string `inbound:"route-exclude-address-set,omitempty"` - IncludeInterface []string `inbound:"include-interface,omitempty"` - ExcludeInterface []string `inbound:"exclude-interface,omitempty"` - IncludeUID []uint32 `inbound:"include-uid,omitempty"` - IncludeUIDRange []string `inbound:"include-uid-range,omitempty"` - ExcludeUID []uint32 `inbound:"exclude-uid,omitempty"` - ExcludeUIDRange []string `inbound:"exclude-uid-range,omitempty"` - ExcludeSrcPort []uint16 `inbound:"exclude-src-port,omitempty"` - ExcludeSrcPortRange []string `inbound:"exclude-src-port-range,omitempty"` - ExcludeDstPort []uint16 `inbound:"exclude-dst-port,omitempty"` - ExcludeDstPortRange []string `inbound:"exclude-dst-port-range,omitempty"` - IncludeAndroidUser []int `inbound:"include-android-user,omitempty"` - IncludePackage []string `inbound:"include-package,omitempty"` - ExcludePackage []string `inbound:"exclude-package,omitempty"` - EndpointIndependentNat bool `inbound:"endpoint-independent-nat,omitempty"` - UDPTimeout int64 `inbound:"udp-timeout,omitempty"` - FileDescriptor int `inbound:"file-descriptor,omitempty"` + MTU uint32 `inbound:"mtu,omitempty"` + GSO bool `inbound:"gso,omitempty"` + GSOMaxSize uint32 `inbound:"gso-max-size,omitempty"` + Inet4Address []netip.Prefix `inbound:"inet4-address,omitempty"` + Inet6Address []netip.Prefix `inbound:"inet6-address,omitempty"` + IPRoute2TableIndex int `inbound:"iproute2-table-index,omitempty"` + IPRoute2RuleIndex int `inbound:"iproute2-rule-index,omitempty"` + AutoRedirect bool `inbound:"auto-redirect,omitempty"` + AutoRedirectInputMark uint32 `inbound:"auto-redirect-input-mark,omitempty"` + AutoRedirectOutputMark uint32 `inbound:"auto-redirect-output-mark,omitempty"` + LoopbackAddress []netip.Addr `inbound:"loopback-address,omitempty"` + StrictRoute bool `inbound:"strict-route,omitempty"` + RouteAddress []netip.Prefix `inbound:"route-address,omitempty"` + RouteAddressSet []string `inbound:"route-address-set,omitempty"` + RouteExcludeAddress []netip.Prefix `inbound:"route-exclude-address,omitempty"` + RouteExcludeAddressSet []string `inbound:"route-exclude-address-set,omitempty"` + IncludeInterface []string `inbound:"include-interface,omitempty"` + ExcludeInterface []string `inbound:"exclude-interface,omitempty"` + IncludeUID []uint32 `inbound:"include-uid,omitempty"` + IncludeUIDRange []string `inbound:"include-uid-range,omitempty"` + ExcludeUID []uint32 `inbound:"exclude-uid,omitempty"` + ExcludeUIDRange []string `inbound:"exclude-uid-range,omitempty"` + ExcludeSrcPort []uint16 `inbound:"exclude-src-port,omitempty"` + ExcludeSrcPortRange []string `inbound:"exclude-src-port-range,omitempty"` + ExcludeDstPort []uint16 `inbound:"exclude-dst-port,omitempty"` + ExcludeDstPortRange []string `inbound:"exclude-dst-port-range,omitempty"` + IncludeAndroidUser []int `inbound:"include-android-user,omitempty"` + IncludePackage []string `inbound:"include-package,omitempty"` + ExcludePackage []string `inbound:"exclude-package,omitempty"` + EndpointIndependentNat bool `inbound:"endpoint-independent-nat,omitempty"` + UDPTimeout int64 `inbound:"udp-timeout,omitempty"` + FileDescriptor int `inbound:"file-descriptor,omitempty"` - Inet4RouteAddress []string `inbound:"inet4-route-address,omitempty"` - Inet6RouteAddress []string `inbound:"inet6-route-address,omitempty"` - Inet4RouteExcludeAddress []string `inbound:"inet4-route-exclude-address,omitempty"` - Inet6RouteExcludeAddress []string `inbound:"inet6-route-exclude-address,omitempty"` + Inet4RouteAddress []netip.Prefix `inbound:"inet4-route-address,omitempty"` + Inet6RouteAddress []netip.Prefix `inbound:"inet6-route-address,omitempty"` + Inet4RouteExcludeAddress []netip.Prefix `inbound:"inet4-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 { return optionToString(o) == optionToString(config) } @@ -72,68 +77,31 @@ func NewTun(options *TunOption) (*Tun, error) { if err != nil { 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{ Base: base, config: options, tun: LC.Tun{ Enable: true, Device: options.Device, - Stack: stack, + Stack: options.Stack, DNSHijack: options.DNSHijack, AutoRoute: options.AutoRoute, AutoDetectInterface: options.AutoDetectInterface, MTU: options.MTU, GSO: options.GSO, GSOMaxSize: options.GSOMaxSize, - Inet4Address: inet4Address, - Inet6Address: inet6Address, + Inet4Address: options.Inet4Address, + Inet6Address: options.Inet6Address, IPRoute2TableIndex: options.IPRoute2TableIndex, IPRoute2RuleIndex: options.IPRoute2RuleIndex, AutoRedirect: options.AutoRedirect, AutoRedirectInputMark: options.AutoRedirectInputMark, AutoRedirectOutputMark: options.AutoRedirectOutputMark, + LoopbackAddress: options.LoopbackAddress, StrictRoute: options.StrictRoute, - RouteAddress: routeAddress, + RouteAddress: options.RouteAddress, RouteAddressSet: options.RouteAddressSet, - RouteExcludeAddress: routeExcludeAddress, + RouteExcludeAddress: options.RouteExcludeAddress, RouteExcludeAddressSet: options.RouteExcludeAddressSet, IncludeInterface: options.IncludeInterface, ExcludeInterface: options.ExcludeInterface, @@ -152,10 +120,10 @@ func NewTun(options *TunOption) (*Tun, error) { UDPTimeout: options.UDPTimeout, FileDescriptor: options.FileDescriptor, - Inet4RouteAddress: inet4RouteAddress, - Inet6RouteAddress: inet6RouteAddress, - Inet4RouteExcludeAddress: inet4RouteExcludeAddress, - Inet6RouteExcludeAddress: inet6RouteExcludeAddress, + Inet4RouteAddress: options.Inet4RouteAddress, + Inet6RouteAddress: options.Inet6RouteAddress, + Inet4RouteExcludeAddress: options.Inet4RouteExcludeAddress, + Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress, }, }, nil } diff --git a/listener/parse.go b/listener/parse.go index adc206c1..8aec050e 100644 --- a/listener/parse.go +++ b/listener/parse.go @@ -64,7 +64,7 @@ func ParseListener(mapping map[string]any) (C.InboundListener, error) { listener, err = IN.NewTunnel(tunnelOption) case "tun": tunOption := &IN.TunOption{ - Stack: C.TunGvisor.String(), + Stack: C.TunGvisor, DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query } err = decoder.Decode(mapping, tunOption) diff --git a/listener/sing_tun/server.go b/listener/sing_tun/server.go index 6998f0c1..b0528557 100644 --- a/listener/sing_tun/server.go +++ b/listener/sing_tun/server.go @@ -347,6 +347,8 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis IPRoute2RuleIndex: ruleIndex, AutoRedirectInputMark: inputMark, AutoRedirectOutputMark: outputMark, + Inet4LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is4), + Inet6LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is6), StrictRoute: options.StrictRoute, Inet4RouteAddress: inet4RouteAddress, Inet6RouteAddress: inet6RouteAddress,