1
0
mirror of https://github.com/MetaCubeX/mihomo.git synced 2025-07-18 17:08:06 +08:00

feat: add loopback-address support for tun

This commit is contained in:
wwqgtxx
2025-06-11 17:45:28 +08:00
parent ae7967f662
commit 9283cb0f5f
9 changed files with 80 additions and 109 deletions

View File

@ -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

View File

@ -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,

2
go.mod
View File

@ -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

4
go.sum
View File

@ -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=

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)

View File

@ -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,