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:
@ -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
|
||||
|
@ -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
2
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
|
||||
|
4
go.sum
4
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=
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
Reference in New Issue
Block a user