Compare commits
21 Commits
v1.3-beta7
...
v1.2.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ce4e31fc8 | ||
|
|
d2d4faf520 | ||
|
|
438de36749 | ||
|
|
df0eef770e | ||
|
|
bbdd495ed5 | ||
|
|
d686172854 | ||
|
|
e1d96cb64e | ||
|
|
d5f94b65b7 | ||
|
|
ec2d0b6b3c | ||
|
|
3a92bf993d | ||
|
|
ec13965fd0 | ||
|
|
ddf747006e | ||
|
|
4382093868 | ||
|
|
a5322850b3 | ||
|
|
407b08975c | ||
|
|
c7067ff5e8 | ||
|
|
9b2384b296 | ||
|
|
b498a22972 | ||
|
|
20e9da5c67 | ||
|
|
ec8974673b | ||
|
|
5e6e7923e4 |
@@ -13,7 +13,6 @@ type ClashServer interface {
|
|||||||
PreStarter
|
PreStarter
|
||||||
Mode() string
|
Mode() string
|
||||||
StoreSelected() bool
|
StoreSelected() bool
|
||||||
StoreFakeIP() bool
|
|
||||||
CacheFile() ClashCacheFile
|
CacheFile() ClashCacheFile
|
||||||
HistoryStorage() *urltest.HistoryStorage
|
HistoryStorage() *urltest.HistoryStorage
|
||||||
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
||||||
@@ -23,7 +22,6 @@ type ClashServer interface {
|
|||||||
type ClashCacheFile interface {
|
type ClashCacheFile interface {
|
||||||
LoadSelected(group string) string
|
LoadSelected(group string) string
|
||||||
StoreSelected(group string, selected string) error
|
StoreSelected(group string, selected string) error
|
||||||
FakeIPStorage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tracker interface {
|
type Tracker interface {
|
||||||
@@ -35,11 +33,6 @@ type OutboundGroup interface {
|
|||||||
All() []string
|
All() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type URLTestGroup interface {
|
|
||||||
OutboundGroup
|
|
||||||
URLTest(ctx context.Context, url string) (map[string]uint16, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func OutboundTag(detour Outbound) string {
|
func OutboundTag(detour Outbound) string {
|
||||||
if group, isGroup := detour.(OutboundGroup); isGroup {
|
if group, isGroup := detour.(OutboundGroup); isGroup {
|
||||||
return group.Now()
|
return group.Now()
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FakeIPStore interface {
|
|
||||||
Service
|
|
||||||
Contains(address netip.Addr) bool
|
|
||||||
Create(domain string, strategy dns.DomainStrategy) (netip.Addr, error)
|
|
||||||
Lookup(address netip.Addr) (string, bool)
|
|
||||||
Reset() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type FakeIPStorage interface {
|
|
||||||
FakeIPMetadata() *FakeIPMetadata
|
|
||||||
FakeIPSaveMetadata(metadata *FakeIPMetadata) error
|
|
||||||
FakeIPStore(address netip.Addr, domain string) error
|
|
||||||
FakeIPLoad(address netip.Addr) (string, bool)
|
|
||||||
FakeIPReset() error
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding"
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FakeIPMetadata struct {
|
|
||||||
Inet4Range netip.Prefix
|
|
||||||
Inet6Range netip.Prefix
|
|
||||||
Inet4Current netip.Addr
|
|
||||||
Inet6Current netip.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *FakeIPMetadata) MarshalBinary() (data []byte, err error) {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
for _, marshaler := range []encoding.BinaryMarshaler{m.Inet4Range, m.Inet6Range, m.Inet4Current, m.Inet6Current} {
|
|
||||||
data, err = marshaler.MarshalBinary()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
common.Must(binary.Write(&buffer, binary.BigEndian, uint16(len(data))))
|
|
||||||
buffer.Write(data)
|
|
||||||
}
|
|
||||||
data = buffer.Bytes()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *FakeIPMetadata) UnmarshalBinary(data []byte) error {
|
|
||||||
reader := bytes.NewReader(data)
|
|
||||||
for _, unmarshaler := range []encoding.BinaryUnmarshaler{&m.Inet4Range, &m.Inet6Range, &m.Inet4Current, &m.Inet6Current} {
|
|
||||||
var length uint16
|
|
||||||
common.Must(binary.Read(reader, binary.BigEndian, &length))
|
|
||||||
element := make([]byte, length)
|
|
||||||
_, err := io.ReadFull(reader, element)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = unmarshaler.UnmarshalBinary(element)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -27,7 +27,7 @@ type InjectableInbound interface {
|
|||||||
type InboundContext struct {
|
type InboundContext struct {
|
||||||
Inbound string
|
Inbound string
|
||||||
InboundType string
|
InboundType string
|
||||||
IPVersion uint8
|
IPVersion int
|
||||||
Network string
|
Network string
|
||||||
Source M.Socksaddr
|
Source M.Socksaddr
|
||||||
Destination M.Socksaddr
|
Destination M.Socksaddr
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,8 +17,3 @@ type Outbound interface {
|
|||||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type IPOutbound interface {
|
|
||||||
Outbound
|
|
||||||
NewIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) (tun.DirectDestination, error)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -21,13 +21,8 @@ type Router interface {
|
|||||||
Outbound(tag string) (Outbound, bool)
|
Outbound(tag string) (Outbound, bool)
|
||||||
DefaultOutbound(network string) Outbound
|
DefaultOutbound(network string) Outbound
|
||||||
|
|
||||||
FakeIPStore() FakeIPStore
|
|
||||||
|
|
||||||
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
RouteIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) tun.RouteAction
|
|
||||||
|
|
||||||
NatRequired(outbound string) bool
|
|
||||||
|
|
||||||
GeoIPReader() *geoip.Reader
|
GeoIPReader() *geoip.Reader
|
||||||
LoadGeosite(code string) (Rule, error)
|
LoadGeosite(code string) (Rule, error)
|
||||||
@@ -37,6 +32,7 @@ type Router interface {
|
|||||||
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
|
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
|
||||||
|
|
||||||
InterfaceFinder() control.InterfaceFinder
|
InterfaceFinder() control.InterfaceFinder
|
||||||
|
UpdateInterfaces() error
|
||||||
DefaultInterface() string
|
DefaultInterface() string
|
||||||
AutoDetectInterface() bool
|
AutoDetectInterface() bool
|
||||||
AutoDetectInterfaceFunc() control.Func
|
AutoDetectInterfaceFunc() control.Func
|
||||||
@@ -44,9 +40,7 @@ type Router interface {
|
|||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
PackageManager() tun.PackageManager
|
PackageManager() tun.PackageManager
|
||||||
|
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
IPRules() []IPRule
|
|
||||||
|
|
||||||
TimeService
|
TimeService
|
||||||
|
|
||||||
@@ -83,12 +77,6 @@ type Rule interface {
|
|||||||
type DNSRule interface {
|
type DNSRule interface {
|
||||||
Rule
|
Rule
|
||||||
DisableCache() bool
|
DisableCache() bool
|
||||||
RewriteTTL() *uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
type IPRule interface {
|
|
||||||
Rule
|
|
||||||
Action() tun.ActionType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type InterfaceUpdateListener interface {
|
type InterfaceUpdateListener interface {
|
||||||
|
|||||||
6
box.go
6
box.go
@@ -133,6 +133,12 @@ func New(options Options) (*Box, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if options.PlatformInterface != nil {
|
||||||
|
err = options.PlatformInterface.Initialize(ctx, router)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize platform interface")
|
||||||
|
}
|
||||||
|
}
|
||||||
preServices := make(map[string]adapter.Service)
|
preServices := make(map[string]adapter.Service)
|
||||||
postServices := make(map[string]adapter.Service)
|
postServices := make(map[string]adapter.Service)
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func TestMergeJSON(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Type: C.RuleTypeDefault,
|
Type: C.RuleTypeDefault,
|
||||||
DefaultOptions: option.DefaultRule{
|
DefaultOptions: option.DefaultRule{
|
||||||
Network: []string{N.NetworkTCP},
|
Network: N.NetworkTCP,
|
||||||
Outbound: "direct",
|
Outbound: "direct",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -42,7 +42,7 @@ func TestMergeJSON(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Type: C.RuleTypeDefault,
|
Type: C.RuleTypeDefault,
|
||||||
DefaultOptions: option.DefaultRule{
|
DefaultOptions: option.DefaultRule{
|
||||||
Network: []string{N.NetworkUDP},
|
Network: N.NetworkUDP,
|
||||||
Outbound: "direct",
|
Outbound: "direct",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
package badversion
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Version struct {
|
|
||||||
Major int
|
|
||||||
Minor int
|
|
||||||
Patch int
|
|
||||||
PreReleaseIdentifier string
|
|
||||||
PreReleaseVersion int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) After(anotherVersion Version) bool {
|
|
||||||
if v.Major > anotherVersion.Major {
|
|
||||||
return true
|
|
||||||
} else if v.Major < anotherVersion.Major {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v.Minor > anotherVersion.Minor {
|
|
||||||
return true
|
|
||||||
} else if v.Minor < anotherVersion.Minor {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v.Patch > anotherVersion.Patch {
|
|
||||||
return true
|
|
||||||
} else if v.Patch < anotherVersion.Patch {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v.PreReleaseIdentifier == "" && anotherVersion.PreReleaseIdentifier != "" {
|
|
||||||
return true
|
|
||||||
} else if v.PreReleaseIdentifier != "" && anotherVersion.PreReleaseIdentifier == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v.PreReleaseIdentifier != "" && anotherVersion.PreReleaseIdentifier != "" {
|
|
||||||
if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "alpha" {
|
|
||||||
return true
|
|
||||||
} else if v.PreReleaseIdentifier == "alpha" && anotherVersion.PreReleaseIdentifier == "beta" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v.PreReleaseVersion > anotherVersion.PreReleaseVersion {
|
|
||||||
return true
|
|
||||||
} else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) String() string {
|
|
||||||
version := F.ToString(v.Major, ".", v.Minor, ".", v.Patch)
|
|
||||||
if v.PreReleaseIdentifier != "" {
|
|
||||||
version = F.ToString(version, "-", v.PreReleaseIdentifier, ".", v.PreReleaseVersion)
|
|
||||||
}
|
|
||||||
return version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) BadString() string {
|
|
||||||
version := F.ToString(v.Major, ".", v.Minor)
|
|
||||||
if v.Patch > 0 {
|
|
||||||
version = F.ToString(version, ".", v.Patch)
|
|
||||||
}
|
|
||||||
if v.PreReleaseIdentifier != "" {
|
|
||||||
version = F.ToString(version, "-", v.PreReleaseIdentifier)
|
|
||||||
if v.PreReleaseVersion > 0 {
|
|
||||||
version = F.ToString(version, v.PreReleaseVersion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return version
|
|
||||||
}
|
|
||||||
|
|
||||||
func Parse(versionName string) (version Version) {
|
|
||||||
if strings.HasPrefix(versionName, "v") {
|
|
||||||
versionName = versionName[1:]
|
|
||||||
}
|
|
||||||
if strings.Contains(versionName, "-") {
|
|
||||||
parts := strings.Split(versionName, "-")
|
|
||||||
versionName = parts[0]
|
|
||||||
identifier := parts[1]
|
|
||||||
if strings.Contains(identifier, ".") {
|
|
||||||
identifierParts := strings.Split(identifier, ".")
|
|
||||||
version.PreReleaseIdentifier = identifierParts[0]
|
|
||||||
if len(identifierParts) >= 2 {
|
|
||||||
version.PreReleaseVersion, _ = strconv.Atoi(identifierParts[1])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if strings.HasPrefix(identifier, "alpha") {
|
|
||||||
version.PreReleaseIdentifier = "alpha"
|
|
||||||
version.PreReleaseVersion, _ = strconv.Atoi(identifier[5:])
|
|
||||||
} else if strings.HasPrefix(identifier, "beta") {
|
|
||||||
version.PreReleaseIdentifier = "beta"
|
|
||||||
version.PreReleaseVersion, _ = strconv.Atoi(identifier[4:])
|
|
||||||
} else {
|
|
||||||
version.PreReleaseIdentifier = identifier
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
versionElements := strings.Split(versionName, ".")
|
|
||||||
versionLen := len(versionElements)
|
|
||||||
if versionLen >= 1 {
|
|
||||||
version.Major, _ = strconv.Atoi(versionElements[0])
|
|
||||||
}
|
|
||||||
if versionLen >= 2 {
|
|
||||||
version.Minor, _ = strconv.Atoi(versionElements[1])
|
|
||||||
}
|
|
||||||
if versionLen >= 3 {
|
|
||||||
version.Patch, _ = strconv.Atoi(versionElements[2])
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package badversion
|
|
||||||
|
|
||||||
import "github.com/sagernet/sing-box/common/json"
|
|
||||||
|
|
||||||
func (v Version) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(v.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Version) UnmarshalJSON(data []byte) error {
|
|
||||||
var version string
|
|
||||||
err := json.Unmarshal(data, &version)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*v = Parse(version)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package badversion
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCompareVersion(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
require.Equal(t, "1.3.0-beta.1", Parse("v1.3.0-beta1").String())
|
|
||||||
require.Equal(t, "1.3-beta1", Parse("v1.3.0-beta.1").BadString())
|
|
||||||
require.True(t, Parse("1.3.0").After(Parse("1.3-beta1")))
|
|
||||||
require.True(t, Parse("1.3.0").After(Parse("1.3.0-beta1")))
|
|
||||||
require.True(t, Parse("1.3.0-beta1").After(Parse("1.3.0-alpha1")))
|
|
||||||
require.True(t, Parse("1.3.1").After(Parse("1.3.0")))
|
|
||||||
require.True(t, Parse("1.4").After(Parse("1.3")))
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||||
"github.com/sagernet/sing-box/common/warning"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
@@ -17,41 +16,6 @@ import (
|
|||||||
"github.com/sagernet/tfo-go"
|
"github.com/sagernet/tfo-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
var warnBindInterfaceOnUnsupportedPlatform = warning.New(
|
|
||||||
func() bool {
|
|
||||||
return !(C.IsLinux || C.IsWindows || C.IsDarwin)
|
|
||||||
},
|
|
||||||
"outbound option `bind_interface` is only supported on Linux and Windows",
|
|
||||||
)
|
|
||||||
|
|
||||||
var warnRoutingMarkOnUnsupportedPlatform = warning.New(
|
|
||||||
func() bool {
|
|
||||||
return !C.IsLinux
|
|
||||||
},
|
|
||||||
"outbound option `routing_mark` is only supported on Linux",
|
|
||||||
)
|
|
||||||
|
|
||||||
var warnReuseAdderOnUnsupportedPlatform = warning.New(
|
|
||||||
func() bool {
|
|
||||||
return !(C.IsDarwin || C.IsDragonfly || C.IsFreebsd || C.IsLinux || C.IsNetbsd || C.IsOpenbsd || C.IsSolaris || C.IsWindows)
|
|
||||||
},
|
|
||||||
"outbound option `reuse_addr` is unsupported on current platform",
|
|
||||||
)
|
|
||||||
|
|
||||||
var warnProtectPathOnNonAndroid = warning.New(
|
|
||||||
func() bool {
|
|
||||||
return !C.IsAndroid
|
|
||||||
},
|
|
||||||
"outbound option `protect_path` is only supported on Android",
|
|
||||||
)
|
|
||||||
|
|
||||||
var warnTFOOnUnsupportedPlatform = warning.New(
|
|
||||||
func() bool {
|
|
||||||
return !(C.IsDarwin || C.IsFreebsd || C.IsLinux || C.IsWindows)
|
|
||||||
},
|
|
||||||
"outbound option `tcp_fast_open` is unsupported on current platform",
|
|
||||||
)
|
|
||||||
|
|
||||||
type DefaultDialer struct {
|
type DefaultDialer struct {
|
||||||
dialer4 tfo.Dialer
|
dialer4 tfo.Dialer
|
||||||
dialer6 tfo.Dialer
|
dialer6 tfo.Dialer
|
||||||
@@ -66,7 +30,6 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
|||||||
var dialer net.Dialer
|
var dialer net.Dialer
|
||||||
var listener net.ListenConfig
|
var listener net.ListenConfig
|
||||||
if options.BindInterface != "" {
|
if options.BindInterface != "" {
|
||||||
warnBindInterfaceOnUnsupportedPlatform.Check()
|
|
||||||
bindFunc := control.BindToInterface(router.InterfaceFinder(), options.BindInterface, -1)
|
bindFunc := control.BindToInterface(router.InterfaceFinder(), options.BindInterface, -1)
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
@@ -80,7 +43,6 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
|||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
}
|
}
|
||||||
if options.RoutingMark != 0 {
|
if options.RoutingMark != 0 {
|
||||||
warnRoutingMarkOnUnsupportedPlatform.Check()
|
|
||||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
|
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
|
||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
|
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
|
||||||
} else if router.DefaultMark() != 0 {
|
} else if router.DefaultMark() != 0 {
|
||||||
@@ -88,11 +50,9 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
|||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
|
listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
|
||||||
}
|
}
|
||||||
if options.ReuseAddr {
|
if options.ReuseAddr {
|
||||||
warnReuseAdderOnUnsupportedPlatform.Check()
|
|
||||||
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
||||||
}
|
}
|
||||||
if options.ProtectPath != "" {
|
if options.ProtectPath != "" {
|
||||||
warnProtectPathOnNonAndroid.Check()
|
|
||||||
dialer.Control = control.Append(dialer.Control, control.ProtectPath(options.ProtectPath))
|
dialer.Control = control.Append(dialer.Control, control.ProtectPath(options.ProtectPath))
|
||||||
listener.Control = control.Append(listener.Control, control.ProtectPath(options.ProtectPath))
|
listener.Control = control.Append(listener.Control, control.ProtectPath(options.ProtectPath))
|
||||||
}
|
}
|
||||||
@@ -101,9 +61,6 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
|||||||
} else {
|
} else {
|
||||||
dialer.Timeout = C.TCPTimeout
|
dialer.Timeout = C.TCPTimeout
|
||||||
}
|
}
|
||||||
if options.TCPFastOpen {
|
|
||||||
warnTFOOnUnsupportedPlatform.Check()
|
|
||||||
}
|
|
||||||
var udpFragment bool
|
var udpFragment bool
|
||||||
if options.UDPFragment != nil {
|
if options.UDPFragment != nil {
|
||||||
udpFragment = *options.UDPFragment
|
udpFragment = *options.UDPFragment
|
||||||
|
|||||||
@@ -27,12 +27,7 @@ type slowOpenConn struct {
|
|||||||
|
|
||||||
func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
|
if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
|
||||||
switch N.NetworkName(network) {
|
return dialer.DialContext(ctx, network, destination.String(), nil)
|
||||||
case N.NetworkTCP, N.NetworkUDP:
|
|
||||||
return dialer.Dialer.DialContext(ctx, network, destination.String())
|
|
||||||
default:
|
|
||||||
return dialer.Dialer.DialContext(ctx, network, destination.AddrString())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return &slowOpenConn{
|
return &slowOpenConn{
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
"github.com/sagernet/sing/common/bufio/deadline"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@@ -69,7 +68,7 @@ func (c *Client) DialContext(ctx context.Context, network string, destination M.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return bufio.NewBindPacketConn(deadline.NewPacketConn(bufio.NewNetPacketConn(&ClientPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination})), destination), nil
|
return bufio.NewUnbindPacketConn(&ClientPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}), nil
|
||||||
default:
|
default:
|
||||||
return nil, E.Extend(N.ErrUnknownNetwork, network)
|
return nil, E.Extend(N.ErrUnknownNetwork, network)
|
||||||
}
|
}
|
||||||
@@ -80,7 +79,7 @@ func (c *Client) ListenPacket(ctx context.Context, destination M.Socksaddr) (net
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return deadline.NewPacketConn(&ClientPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}), nil
|
return &ClientPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) openStream() (net.Conn, error) {
|
func (c *Client) openStream() (net.Conn, error) {
|
||||||
@@ -250,6 +249,10 @@ func (c *ClientConn) WriterReplaceable() bool {
|
|||||||
return c.requestWrite
|
return c.requestWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) NeedAdditionalReadDeadline() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ClientConn) Upstream() any {
|
func (c *ClientConn) Upstream() any {
|
||||||
return c.Conn
|
return c.Conn
|
||||||
}
|
}
|
||||||
@@ -378,6 +381,10 @@ func (c *ClientPacketConn) RemoteAddr() net.Addr {
|
|||||||
return c.destination.UDPAddr()
|
return c.destination.UDPAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) NeedAdditionalReadDeadline() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ClientPacketConn) Upstream() any {
|
func (c *ClientPacketConn) Upstream() any {
|
||||||
return c.ExtendedConn
|
return c.ExtendedConn
|
||||||
}
|
}
|
||||||
@@ -519,6 +526,10 @@ func (c *ClientPacketAddrConn) FrontHeadroom() int {
|
|||||||
return 2 + M.MaxSocksaddrLength
|
return 2 + M.MaxSocksaddrLength
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketAddrConn) NeedAdditionalReadDeadline() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ClientPacketAddrConn) Upstream() any {
|
func (c *ClientPacketAddrConn) Upstream() any {
|
||||||
return c.ExtendedConn
|
return c.ExtendedConn
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
"github.com/sagernet/sing/common/bufio/deadline"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@@ -68,7 +67,7 @@ func newConnection(ctx context.Context, router adapter.Router, errorHandler E.Ha
|
|||||||
logger.InfoContext(ctx, "inbound multiplex packet connection")
|
logger.InfoContext(ctx, "inbound multiplex packet connection")
|
||||||
packetConn = &ServerPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream)}
|
packetConn = &ServerPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream)}
|
||||||
}
|
}
|
||||||
hErr := router.RoutePacketConnection(ctx, deadline.NewPacketConn(bufio.NewNetPacketConn(packetConn)), metadata)
|
hErr := router.RoutePacketConnection(ctx, packetConn, metadata)
|
||||||
stream.Close()
|
stream.Close()
|
||||||
if hErr != nil {
|
if hErr != nil {
|
||||||
errorHandler.NewError(ctx, hErr)
|
errorHandler.NewError(ctx, hErr)
|
||||||
@@ -132,6 +131,10 @@ func (c *ServerConn) FrontHeadroom() int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ServerConn) NeedAdditionalReadDeadline() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ServerConn) Upstream() any {
|
func (c *ServerConn) Upstream() any {
|
||||||
return c.ExtendedConn
|
return c.ExtendedConn
|
||||||
}
|
}
|
||||||
@@ -184,6 +187,10 @@ func (c *ServerPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksad
|
|||||||
return c.ExtendedConn.WriteBuffer(buffer)
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketConn) NeedAdditionalReadDeadline() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ServerPacketConn) Upstream() any {
|
func (c *ServerPacketConn) Upstream() any {
|
||||||
return c.ExtendedConn
|
return c.ExtendedConn
|
||||||
}
|
}
|
||||||
@@ -246,6 +253,10 @@ func (c *ServerPacketAddrConn) WritePacket(buffer *buf.Buffer, destination M.Soc
|
|||||||
return c.ExtendedConn.WriteBuffer(buffer)
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketAddrConn) NeedAdditionalReadDeadline() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ServerPacketAddrConn) Upstream() any {
|
func (c *ServerPacketAddrConn) Upstream() any {
|
||||||
return c.ExtendedConn
|
return c.ExtendedConn
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/protocol/http"
|
"github.com/sagernet/sing/protocol/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,5 +16,5 @@ func HTTPHost(ctx context.Context, reader io.Reader) (*adapter.InboundContext, e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &adapter.InboundContext{Protocol: C.ProtocolHTTP, Domain: request.Host}, nil
|
return &adapter.InboundContext{Protocol: C.ProtocolHTTP, Domain: M.ParseSocksaddr(request.Host).AddrString()}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
27
common/sniff/http_test.go
Normal file
27
common/sniff/http_test.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package sniff_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/sniff"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSniffHTTP1(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
pkt := "GET / HTTP/1.1\r\nHost: www.google.com\r\nAccept: */*\r\n\r\n"
|
||||||
|
metadata, err := sniff.HTTPHost(context.Background(), strings.NewReader(pkt))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, metadata.Domain, "www.google.com")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSniffHTTP1WithPort(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
pkt := "GET / HTTP/1.1\r\nHost: www.gov.cn:8080\r\nAccept: */*\r\n\r\n"
|
||||||
|
metadata, err := sniff.HTTPHost(context.Background(), strings.NewReader(pkt))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, metadata.Domain, "www.gov.cn")
|
||||||
|
}
|
||||||
@@ -50,9 +50,6 @@ func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
|
func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
|
||||||
if link == "" {
|
|
||||||
link = "https://www.gstatic.com/generate_204"
|
|
||||||
}
|
|
||||||
linkURL, err := url.Parse(link)
|
linkURL, err := url.Parse(link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
package warning
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Warning struct {
|
|
||||||
logger log.Logger
|
|
||||||
check CheckFunc
|
|
||||||
message string
|
|
||||||
checkOnce sync.Once
|
|
||||||
}
|
|
||||||
|
|
||||||
type CheckFunc = func() bool
|
|
||||||
|
|
||||||
func New(checkFunc CheckFunc, message string) Warning {
|
|
||||||
return Warning{
|
|
||||||
check: checkFunc,
|
|
||||||
message: message,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Warning) Check() {
|
|
||||||
w.checkOnce.Do(func() {
|
|
||||||
if w.check() {
|
|
||||||
log.Warn(w.message)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,6 @@ const dirName = "sing-box"
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
basePath string
|
basePath string
|
||||||
tempPath string
|
|
||||||
resourcePaths []string
|
resourcePaths []string
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,21 +22,10 @@ func BasePath(name string) string {
|
|||||||
return filepath.Join(basePath, name)
|
return filepath.Join(basePath, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateTemp(pattern string) (*os.File, error) {
|
|
||||||
if tempPath == "" {
|
|
||||||
tempPath = os.TempDir()
|
|
||||||
}
|
|
||||||
return os.CreateTemp(tempPath, pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetBasePath(path string) {
|
func SetBasePath(path string) {
|
||||||
basePath = path
|
basePath = path
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetTempPath(path string) {
|
|
||||||
tempPath = path
|
|
||||||
}
|
|
||||||
|
|
||||||
func FindPath(name string) (string, bool) {
|
func FindPath(name string) (string, bool) {
|
||||||
name = os.ExpandEnv(name)
|
name = os.ExpandEnv(name)
|
||||||
if rw.FileExists(name) {
|
if rw.FileExists(name) {
|
||||||
|
|||||||
@@ -1,54 +1,7 @@
|
|||||||
#### 1.3-beta7
|
#### 1.2.6
|
||||||
|
|
||||||
* Add `path` and `headers` options for HTTP outbound
|
|
||||||
* Add multi-user support for Shadowsocks legacy AEAD inbound
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.2.4
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.3-beta6
|
|
||||||
|
|
||||||
* Fix WireGuard reconnect
|
|
||||||
* Perform URLTest recheck after network changes
|
|
||||||
* Fix bugs and update dependencies
|
* Fix bugs and update dependencies
|
||||||
|
|
||||||
#### 1.3-beta5
|
|
||||||
|
|
||||||
* Add Clash.Meta API compatibility for Clash API
|
|
||||||
* Download Yacd-meta by default if the specified Clash `external_ui` directory is empty
|
|
||||||
* Add path and headers option for HTTP outbound
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.3-beta4
|
|
||||||
|
|
||||||
* Fix bugs
|
|
||||||
|
|
||||||
#### 1.3-beta2
|
|
||||||
|
|
||||||
* Download clash-dashboard if the specified Clash `external_ui` directory is empty
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
#### 1.3-beta1
|
|
||||||
|
|
||||||
* Add [DNS reverse mapping](/configuration/dns#reverse_mapping) support
|
|
||||||
* Add [L3 routing](/configuration/route/ip-rule) support **1**
|
|
||||||
* Add `rewrite_ttl` DNS rule action
|
|
||||||
* Add [FakeIP](/configuration/dns/fakeip) support **2**
|
|
||||||
* Add `store_fakeip` Clash API option
|
|
||||||
* Add multi-peer support for [WireGuard](/configuration/outbound/wireguard#peers) outbound
|
|
||||||
* Add loopback detect
|
|
||||||
|
|
||||||
*1*:
|
|
||||||
|
|
||||||
It can currently be used to [route connections directly to WireGuard](/examples/wireguard-direct) or block connections
|
|
||||||
at the IP layer.
|
|
||||||
|
|
||||||
*2*:
|
|
||||||
|
|
||||||
See [FAQ](/faq/fakeip) for more information.
|
|
||||||
|
|
||||||
#### 1.2.3
|
#### 1.2.3
|
||||||
|
|
||||||
* Introducing our [new Android client application](/installation/clients/sfa)
|
* Introducing our [new Android client application](/installation/clients/sfa)
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
# FakeIP
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"enabled": true,
|
|
||||||
"inet4_range": "198.18.0.0/15",
|
|
||||||
"inet6_range": "fc00::/18"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
#### enabled
|
|
||||||
|
|
||||||
Enable FakeIP service.
|
|
||||||
|
|
||||||
#### inet4_range
|
|
||||||
|
|
||||||
IPv4 address range for FakeIP.
|
|
||||||
|
|
||||||
#### inet6_address
|
|
||||||
|
|
||||||
IPv6 address range for FakeIP.
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# FakeIP
|
|
||||||
|
|
||||||
### 结构
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"enabled": true,
|
|
||||||
"inet4_range": "198.18.0.0/15",
|
|
||||||
"inet6_range": "fc00::/18"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 字段
|
|
||||||
|
|
||||||
#### enabled
|
|
||||||
|
|
||||||
启用 FakeIP 服务。
|
|
||||||
|
|
||||||
#### inet4_range
|
|
||||||
|
|
||||||
用于 FakeIP 的 IPv4 地址范围。
|
|
||||||
|
|
||||||
#### inet6_range
|
|
||||||
|
|
||||||
用于 FakeIP 的 IPv6 地址范围。
|
|
||||||
@@ -10,9 +10,7 @@
|
|||||||
"final": "",
|
"final": "",
|
||||||
"strategy": "",
|
"strategy": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"disable_expire": false,
|
"disable_expire": false
|
||||||
"reverse_mapping": false,
|
|
||||||
"fakeip": {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,7 +22,6 @@
|
|||||||
|----------|--------------------------------|
|
|----------|--------------------------------|
|
||||||
| `server` | List of [DNS Server](./server) |
|
| `server` | List of [DNS Server](./server) |
|
||||||
| `rules` | List of [DNS Rule](./rule) |
|
| `rules` | List of [DNS Rule](./rule) |
|
||||||
| `fakeip` | [FakeIP](./fakeip) |
|
|
||||||
|
|
||||||
#### final
|
#### final
|
||||||
|
|
||||||
@@ -46,15 +43,4 @@ Disable dns cache.
|
|||||||
|
|
||||||
#### disable_expire
|
#### disable_expire
|
||||||
|
|
||||||
Disable dns cache expire.
|
Disable dns cache expire.
|
||||||
|
|
||||||
#### reverse_mapping
|
|
||||||
|
|
||||||
Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing.
|
|
||||||
|
|
||||||
Since this process relies on the act of resolving domain names by an application before making a request, it can be
|
|
||||||
problematic in environments such as macOS, where DNS is proxied and cached by the system.
|
|
||||||
|
|
||||||
#### fakeip
|
|
||||||
|
|
||||||
[FakeIP](./fakeip) settings.
|
|
||||||
@@ -10,9 +10,7 @@
|
|||||||
"final": "",
|
"final": "",
|
||||||
"strategy": "",
|
"strategy": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"disable_expire": false,
|
"disable_expire": false
|
||||||
"reverse_mapping": false,
|
|
||||||
"fakeip": {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,14 +43,4 @@
|
|||||||
|
|
||||||
#### disable_expire
|
#### disable_expire
|
||||||
|
|
||||||
禁用 DNS 缓存过期。
|
禁用 DNS 缓存过期。
|
||||||
|
|
||||||
#### reverse_mapping
|
|
||||||
|
|
||||||
在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。
|
|
||||||
|
|
||||||
由于此过程依赖于应用程序在发出请求之前解析域名的行为,因此在 macOS 等 DNS 由系统代理和缓存的环境中可能会出现问题。
|
|
||||||
|
|
||||||
#### fakeip
|
|
||||||
|
|
||||||
[FakeIP](./fakeip) 设置。
|
|
||||||
@@ -84,16 +84,14 @@
|
|||||||
"direct"
|
"direct"
|
||||||
],
|
],
|
||||||
"server": "local",
|
"server": "local",
|
||||||
"disable_cache": false,
|
"disable_cache": false
|
||||||
"rewrite_ttl": 100
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "logical",
|
"type": "logical",
|
||||||
"mode": "and",
|
"mode": "and",
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"server": "local",
|
"server": "local",
|
||||||
"disable_cache": false,
|
"disable_cache": false
|
||||||
"rewrite_ttl": 100
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -246,10 +244,6 @@ Tag of the target dns server.
|
|||||||
|
|
||||||
Disable cache and save cache in this query.
|
Disable cache and save cache in this query.
|
||||||
|
|
||||||
#### rewrite_ttl
|
|
||||||
|
|
||||||
Rewrite TTL in DNS responses.
|
|
||||||
|
|
||||||
### Logical Fields
|
### Logical Fields
|
||||||
|
|
||||||
#### type
|
#### type
|
||||||
|
|||||||
@@ -243,10 +243,6 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
在此查询中禁用缓存。
|
在此查询中禁用缓存。
|
||||||
|
|
||||||
#### rewrite_ttl
|
|
||||||
|
|
||||||
重写 DNS 回应中的 TTL。
|
|
||||||
|
|
||||||
### 逻辑字段
|
### 逻辑字段
|
||||||
|
|
||||||
#### type
|
#### type
|
||||||
|
|||||||
@@ -30,18 +30,17 @@ The tag of the dns server.
|
|||||||
|
|
||||||
The address of the dns server.
|
The address of the dns server.
|
||||||
|
|
||||||
| Protocol | Format |
|
| Protocol | Format |
|
||||||
|---------------------|-------------------------------|
|
|----------|-------------------------------|
|
||||||
| `System` | `local` |
|
| `System` | `local` |
|
||||||
| `TCP` | `tcp://1.0.0.1` |
|
| `TCP` | `tcp://1.0.0.1` |
|
||||||
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
||||||
| `TLS` | `tls://dns.google` |
|
| `TLS` | `tls://dns.google` |
|
||||||
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
||||||
| `QUIC` | `quic://dns.adguard.com` |
|
| `QUIC` | `quic://dns.adguard.com` |
|
||||||
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
||||||
| `RCode` | `rcode://refused` |
|
| `RCode` | `rcode://refused` |
|
||||||
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
|
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
|
||||||
| [FakeIP](./fakeip) | `fakeip` |
|
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
|
|||||||
@@ -30,18 +30,17 @@ DNS 服务器的标签。
|
|||||||
|
|
||||||
DNS 服务器的地址。
|
DNS 服务器的地址。
|
||||||
|
|
||||||
| 协议 | 格式 |
|
| 协议 | 格式 |
|
||||||
|--------------------|------------------------------|
|
|----------|------------------------------|
|
||||||
| `System` | `local` |
|
| `System` | `local` |
|
||||||
| `TCP` | `tcp://1.0.0.1` |
|
| `TCP` | `tcp://1.0.0.1` |
|
||||||
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
||||||
| `TLS` | `tls://dns.google` |
|
| `TLS` | `tls://dns.google` |
|
||||||
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
||||||
| `QUIC` | `quic://dns.adguard.com` |
|
| `QUIC` | `quic://dns.adguard.com` |
|
||||||
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
||||||
| `RCode` | `rcode://refused` |
|
| `RCode` | `rcode://refused` |
|
||||||
| `DHCP` | `dhcp://auto` 或 `dhcp://en0` |
|
| `DHCP` | `dhcp://auto` 或 `dhcp://en0` |
|
||||||
| [FakeIP](./fakeip) | `fakeip` |
|
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
"clash_api": {
|
"clash_api": {
|
||||||
"external_controller": "127.0.0.1:9090",
|
"external_controller": "127.0.0.1:9090",
|
||||||
"external_ui": "folder",
|
"external_ui": "folder",
|
||||||
"external_ui_download_url": "",
|
|
||||||
"external_ui_download_detour": "",
|
|
||||||
"secret": "",
|
"secret": "",
|
||||||
"default_mode": "rule",
|
"default_mode": "rule",
|
||||||
"store_selected": false,
|
"store_selected": false,
|
||||||
@@ -55,18 +53,6 @@ A relative path to the configuration directory or an absolute path to a
|
|||||||
directory in which you put some static web resource. sing-box will then
|
directory in which you put some static web resource. sing-box will then
|
||||||
serve it at `http://{{external-controller}}/ui`.
|
serve it at `http://{{external-controller}}/ui`.
|
||||||
|
|
||||||
#### external_ui_download_url
|
|
||||||
|
|
||||||
ZIP download URL for the external UI, will be used if the specified `external_ui` directory is empty.
|
|
||||||
|
|
||||||
`https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip` will be used if empty.
|
|
||||||
|
|
||||||
#### external_ui_download_detour
|
|
||||||
|
|
||||||
The tag of the outbound to download the external UI.
|
|
||||||
|
|
||||||
Default outbound will be used if empty.
|
|
||||||
|
|
||||||
#### secret
|
#### secret
|
||||||
|
|
||||||
Secret for the RESTful API (optional)
|
Secret for the RESTful API (optional)
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
"clash_api": {
|
"clash_api": {
|
||||||
"external_controller": "127.0.0.1:9090",
|
"external_controller": "127.0.0.1:9090",
|
||||||
"external_ui": "folder",
|
"external_ui": "folder",
|
||||||
"external_ui_download_url": "",
|
|
||||||
"external_ui_download_detour": "",
|
|
||||||
"secret": "",
|
"secret": "",
|
||||||
"default_mode": "rule",
|
"default_mode": "rule",
|
||||||
"store_selected": false,
|
"store_selected": false,
|
||||||
@@ -53,18 +51,6 @@ RESTful web API 监听地址。如果为空,则禁用 Clash API。
|
|||||||
|
|
||||||
到静态网页资源目录的相对路径或绝对路径。sing-box 会在 `http://{{external-controller}}/ui` 下提供它。
|
到静态网页资源目录的相对路径或绝对路径。sing-box 会在 `http://{{external-controller}}/ui` 下提供它。
|
||||||
|
|
||||||
#### external_ui_download_url
|
|
||||||
|
|
||||||
静态网页资源的 ZIP 下载 URL,如果指定的 `external_ui` 目录为空,将使用。
|
|
||||||
|
|
||||||
默认使用 `https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip`。
|
|
||||||
|
|
||||||
#### external_ui_download_detour
|
|
||||||
|
|
||||||
用于下载静态网页资源的出站的标签。
|
|
||||||
|
|
||||||
如果为空,将使用默认出站。
|
|
||||||
|
|
||||||
#### secret
|
#### secret
|
||||||
|
|
||||||
RESTful API 的密钥(可选)
|
RESTful API 的密钥(可选)
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
"server_port": 1080,
|
"server_port": 1080,
|
||||||
"username": "sekai",
|
"username": "sekai",
|
||||||
"password": "admin",
|
"password": "admin",
|
||||||
"path": "",
|
|
||||||
"headers": {},
|
|
||||||
"tls": {},
|
"tls": {},
|
||||||
|
|
||||||
... // Dial Fields
|
... // Dial Fields
|
||||||
@@ -41,14 +39,6 @@ Basic authorization username.
|
|||||||
|
|
||||||
Basic authorization password.
|
Basic authorization password.
|
||||||
|
|
||||||
#### path
|
|
||||||
|
|
||||||
Path of HTTP request.
|
|
||||||
|
|
||||||
#### headers
|
|
||||||
|
|
||||||
Extra headers of HTTP request.
|
|
||||||
|
|
||||||
#### tls
|
#### tls
|
||||||
|
|
||||||
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
"server_port": 1080,
|
"server_port": 1080,
|
||||||
"username": "sekai",
|
"username": "sekai",
|
||||||
"password": "admin",
|
"password": "admin",
|
||||||
"path": "",
|
|
||||||
"headers": {},
|
|
||||||
"tls": {},
|
"tls": {},
|
||||||
|
|
||||||
... // 拨号字段
|
... // 拨号字段
|
||||||
@@ -41,14 +39,6 @@ Basic 认证用户名。
|
|||||||
|
|
||||||
Basic 认证密码。
|
Basic 认证密码。
|
||||||
|
|
||||||
#### path
|
|
||||||
|
|
||||||
HTTP 请求路径。
|
|
||||||
|
|
||||||
#### headers
|
|
||||||
|
|
||||||
HTTP 请求的额外标头。
|
|
||||||
|
|
||||||
#### tls
|
#### tls
|
||||||
|
|
||||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
|
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
|
||||||
|
|||||||
@@ -37,10 +37,4 @@
|
|||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
The tag of the outbound.
|
The tag of the outbound.
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
#### Outbounds that support IP connection
|
|
||||||
|
|
||||||
* `WireGuard`
|
|
||||||
@@ -36,10 +36,4 @@
|
|||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
出站的标签。
|
出站的标签。
|
||||||
|
|
||||||
### 特性
|
|
||||||
|
|
||||||
#### 支持 IP 连接的出站
|
|
||||||
|
|
||||||
* `WireGuard`
|
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
"proxy-b",
|
"proxy-b",
|
||||||
"proxy-c"
|
"proxy-c"
|
||||||
],
|
],
|
||||||
"url": "https://www.gstatic.com/generate_204",
|
"url": "http://www.gstatic.com/generate_204",
|
||||||
"interval": "1m",
|
"interval": "1m",
|
||||||
"tolerance": 50
|
"tolerance": 50
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@ List of outbound tags to test.
|
|||||||
|
|
||||||
#### url
|
#### url
|
||||||
|
|
||||||
The URL to test. `https://www.gstatic.com/generate_204` will be used if empty.
|
The URL to test. `http://www.gstatic.com/generate_204` will be used if empty.
|
||||||
|
|
||||||
#### interval
|
#### interval
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"proxy-b",
|
"proxy-b",
|
||||||
"proxy-c"
|
"proxy-c"
|
||||||
],
|
],
|
||||||
"url": "https://www.gstatic.com/generate_204",
|
"url": "http://www.gstatic.com/generate_204",
|
||||||
"interval": "1m",
|
"interval": "1m",
|
||||||
"tolerance": 50
|
"tolerance": 50
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
#### url
|
#### url
|
||||||
|
|
||||||
用于测试的链接。默认使用 `https://www.gstatic.com/generate_204`。
|
用于测试的链接。默认使用 `http://www.gstatic.com/generate_204`。
|
||||||
|
|
||||||
#### interval
|
#### interval
|
||||||
|
|
||||||
|
|||||||
@@ -13,18 +13,6 @@
|
|||||||
"10.0.0.2/32"
|
"10.0.0.2/32"
|
||||||
],
|
],
|
||||||
"private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=",
|
"private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=",
|
||||||
"peers": [
|
|
||||||
{
|
|
||||||
"server": "127.0.0.1",
|
|
||||||
"server_port": 1080,
|
|
||||||
"public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=",
|
|
||||||
"pre_shared_key": "31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=",
|
|
||||||
"allowed_ips": [
|
|
||||||
"0.0.0.0/0"
|
|
||||||
],
|
|
||||||
"reserved": [0, 0, 0]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=",
|
"peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=",
|
||||||
"pre_shared_key": "31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=",
|
"pre_shared_key": "31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=",
|
||||||
"reserved": [0, 0, 0],
|
"reserved": [0, 0, 0],
|
||||||
@@ -48,13 +36,13 @@
|
|||||||
|
|
||||||
#### server
|
#### server
|
||||||
|
|
||||||
==Required if multi-peer disabled==
|
==Required==
|
||||||
|
|
||||||
The server address.
|
The server address.
|
||||||
|
|
||||||
#### server_port
|
#### server_port
|
||||||
|
|
||||||
==Required if multi-peer disabled==
|
==Required==
|
||||||
|
|
||||||
The server port.
|
The server port.
|
||||||
|
|
||||||
@@ -87,25 +75,9 @@ wg genkey
|
|||||||
echo "private key" || wg pubkey
|
echo "private key" || wg pubkey
|
||||||
```
|
```
|
||||||
|
|
||||||
#### peers
|
|
||||||
|
|
||||||
Multi-peer support.
|
|
||||||
|
|
||||||
If enabled, `server, server_port, peer_public_key, pre_shared_key` will be ignored.
|
|
||||||
|
|
||||||
#### peers.allowed_ips
|
|
||||||
|
|
||||||
WireGuard allowed IPs.
|
|
||||||
|
|
||||||
#### peers.reserved
|
|
||||||
|
|
||||||
WireGuard reserved field bytes.
|
|
||||||
|
|
||||||
`$outbound.reserved` will be used if empty.
|
|
||||||
|
|
||||||
#### peer_public_key
|
#### peer_public_key
|
||||||
|
|
||||||
==Required if multi-peer disabled==
|
==Required==
|
||||||
|
|
||||||
WireGuard peer public key.
|
WireGuard peer public key.
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
"route": {
|
"route": {
|
||||||
"geoip": {},
|
"geoip": {},
|
||||||
"geosite": {},
|
"geosite": {},
|
||||||
"ip_rules": [],
|
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"final": "",
|
"final": "",
|
||||||
"auto_detect_interface": false,
|
"auto_detect_interface": false,
|
||||||
@@ -20,12 +19,11 @@
|
|||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
| Key | Format |
|
| Key | Format |
|
||||||
|------------|------------------------------------|
|
|-----------|------------------------------|
|
||||||
| `geoip` | [GeoIP](./geoip) |
|
| `geoip` | [GeoIP](./geoip) |
|
||||||
| `geosite` | [Geosite](./geosite) |
|
| `geosite` | [Geosite](./geosite) |
|
||||||
| `ip_rules` | List of [IP Route Rule](./ip-rule) |
|
| `rules` | List of [Route Rule](./rule) |
|
||||||
| `rules` | List of [Route Rule](./rule) |
|
|
||||||
|
|
||||||
#### final
|
#### final
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
"route": {
|
"route": {
|
||||||
"geoip": {},
|
"geoip": {},
|
||||||
"geosite": {},
|
"geosite": {},
|
||||||
"ip_rules": [],
|
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"final": "",
|
"final": "",
|
||||||
"auto_detect_interface": false,
|
"auto_detect_interface": false,
|
||||||
@@ -20,12 +19,11 @@
|
|||||||
|
|
||||||
### 字段
|
### 字段
|
||||||
|
|
||||||
| 键 | 格式 |
|
| 键 | 格式 |
|
||||||
|------------|-------------------------|
|
|-----------|----------------------|
|
||||||
| `geoip` | [GeoIP](./geoip) |
|
| `geoip` | [GeoIP](./geoip) |
|
||||||
| `geosite` | [GeoSite](./geosite) |
|
| `geosite` | [GeoSite](./geosite) |
|
||||||
| `ip_rules` | 一组 [IP 路由规则](./ip-rule) |
|
| `rules` | 一组 [路由规则](./rule) |
|
||||||
| `rules` | 一组 [路由规则](./rule) |
|
|
||||||
|
|
||||||
#### final
|
#### final
|
||||||
|
|
||||||
@@ -67,4 +65,4 @@
|
|||||||
|
|
||||||
默认为出站连接设置路由标记。
|
默认为出站连接设置路由标记。
|
||||||
|
|
||||||
如果设置了 `outbound.routing_mark` 设置,则不生效。
|
如果设置了 `outbound.routing_mark` 设置,则不生效。
|
||||||
|
|||||||
@@ -1,205 +0,0 @@
|
|||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"route": {
|
|
||||||
"ip_rules": [
|
|
||||||
{
|
|
||||||
"inbound": [
|
|
||||||
"mixed-in"
|
|
||||||
],
|
|
||||||
"ip_version": 6,
|
|
||||||
"network": [
|
|
||||||
"tcp"
|
|
||||||
],
|
|
||||||
"domain": [
|
|
||||||
"test.com"
|
|
||||||
],
|
|
||||||
"domain_suffix": [
|
|
||||||
".cn"
|
|
||||||
],
|
|
||||||
"domain_keyword": [
|
|
||||||
"test"
|
|
||||||
],
|
|
||||||
"domain_regex": [
|
|
||||||
"^stun\\..+"
|
|
||||||
],
|
|
||||||
"geosite": [
|
|
||||||
"cn"
|
|
||||||
],
|
|
||||||
"source_geoip": [
|
|
||||||
"private"
|
|
||||||
],
|
|
||||||
"geoip": [
|
|
||||||
"cn"
|
|
||||||
],
|
|
||||||
"source_ip_cidr": [
|
|
||||||
"10.0.0.0/24",
|
|
||||||
"192.168.0.1"
|
|
||||||
],
|
|
||||||
"ip_cidr": [
|
|
||||||
"10.0.0.0/24",
|
|
||||||
"192.168.0.1"
|
|
||||||
],
|
|
||||||
"source_port": [
|
|
||||||
12345
|
|
||||||
],
|
|
||||||
"source_port_range": [
|
|
||||||
"1000:2000",
|
|
||||||
":3000",
|
|
||||||
"4000:"
|
|
||||||
],
|
|
||||||
"port": [
|
|
||||||
80,
|
|
||||||
443
|
|
||||||
],
|
|
||||||
"port_range": [
|
|
||||||
"1000:2000",
|
|
||||||
":3000",
|
|
||||||
"4000:"
|
|
||||||
],
|
|
||||||
"invert": false,
|
|
||||||
"action": "direct",
|
|
||||||
"outbound": "wireguard"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "logical",
|
|
||||||
"mode": "and",
|
|
||||||
"rules": [],
|
|
||||||
"invert": false,
|
|
||||||
"action": "direct",
|
|
||||||
"outbound": "wireguard"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! note ""
|
|
||||||
|
|
||||||
You can ignore the JSON Array [] tag when the content is only one item
|
|
||||||
|
|
||||||
### Default Fields
|
|
||||||
|
|
||||||
!!! note ""
|
|
||||||
|
|
||||||
The default rule uses the following matching logic:
|
|
||||||
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr`) &&
|
|
||||||
(`port` || `port_range`) &&
|
|
||||||
(`source_geoip` || `source_ip_cidr`) &&
|
|
||||||
(`source_port` || `source_port_range`) &&
|
|
||||||
`other fields`
|
|
||||||
|
|
||||||
#### inbound
|
|
||||||
|
|
||||||
Tags of [Inbound](/configuration/inbound).
|
|
||||||
|
|
||||||
#### ip_version
|
|
||||||
|
|
||||||
4 or 6.
|
|
||||||
|
|
||||||
Not limited if empty.
|
|
||||||
|
|
||||||
#### network
|
|
||||||
|
|
||||||
Match network protocol.
|
|
||||||
|
|
||||||
Available values:
|
|
||||||
|
|
||||||
* `tcp`
|
|
||||||
* `udp`
|
|
||||||
* `icmpv4`
|
|
||||||
* `icmpv6`
|
|
||||||
|
|
||||||
#### domain
|
|
||||||
|
|
||||||
Match full domain.
|
|
||||||
|
|
||||||
#### domain_suffix
|
|
||||||
|
|
||||||
Match domain suffix.
|
|
||||||
|
|
||||||
#### domain_keyword
|
|
||||||
|
|
||||||
Match domain using keyword.
|
|
||||||
|
|
||||||
#### domain_regex
|
|
||||||
|
|
||||||
Match domain using regular expression.
|
|
||||||
|
|
||||||
#### geosite
|
|
||||||
|
|
||||||
Match geosite.
|
|
||||||
|
|
||||||
#### source_geoip
|
|
||||||
|
|
||||||
Match source geoip.
|
|
||||||
|
|
||||||
#### geoip
|
|
||||||
|
|
||||||
Match geoip.
|
|
||||||
|
|
||||||
#### source_ip_cidr
|
|
||||||
|
|
||||||
Match source ip cidr.
|
|
||||||
|
|
||||||
#### ip_cidr
|
|
||||||
|
|
||||||
Match ip cidr.
|
|
||||||
|
|
||||||
#### source_port
|
|
||||||
|
|
||||||
Match source port.
|
|
||||||
|
|
||||||
#### source_port_range
|
|
||||||
|
|
||||||
Match source port range.
|
|
||||||
|
|
||||||
#### port
|
|
||||||
|
|
||||||
Match port.
|
|
||||||
|
|
||||||
#### port_range
|
|
||||||
|
|
||||||
Match port range.
|
|
||||||
|
|
||||||
#### invert
|
|
||||||
|
|
||||||
Invert match result.
|
|
||||||
|
|
||||||
#### action
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
| Action | Description |
|
|
||||||
|--------|--------------------------------------------------------------------|
|
|
||||||
| return | Stop IP routing and assemble the connection to the transport layer |
|
|
||||||
| block | Block the connection |
|
|
||||||
| direct | Directly forward the connection |
|
|
||||||
|
|
||||||
#### outbound
|
|
||||||
|
|
||||||
==Required if action is direct==
|
|
||||||
|
|
||||||
Tag of the target outbound.
|
|
||||||
|
|
||||||
Only outbound which supports IP connection can be used, see [Outbounds that support IP connection](/configuration/outbound/#outbounds-that-support-ip-connection).
|
|
||||||
|
|
||||||
### Logical Fields
|
|
||||||
|
|
||||||
#### type
|
|
||||||
|
|
||||||
`logical`
|
|
||||||
|
|
||||||
#### mode
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
`and` or `or`
|
|
||||||
|
|
||||||
#### rules
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Included default rules.
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
### 结构
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"route": {
|
|
||||||
"ip_rules": [
|
|
||||||
{
|
|
||||||
"inbound": [
|
|
||||||
"mixed-in"
|
|
||||||
],
|
|
||||||
"ip_version": 6,
|
|
||||||
"network": [
|
|
||||||
"tcp"
|
|
||||||
],
|
|
||||||
"domain": [
|
|
||||||
"test.com"
|
|
||||||
],
|
|
||||||
"domain_suffix": [
|
|
||||||
".cn"
|
|
||||||
],
|
|
||||||
"domain_keyword": [
|
|
||||||
"test"
|
|
||||||
],
|
|
||||||
"domain_regex": [
|
|
||||||
"^stun\\..+"
|
|
||||||
],
|
|
||||||
"geosite": [
|
|
||||||
"cn"
|
|
||||||
],
|
|
||||||
"source_geoip": [
|
|
||||||
"private"
|
|
||||||
],
|
|
||||||
"geoip": [
|
|
||||||
"cn"
|
|
||||||
],
|
|
||||||
"source_ip_cidr": [
|
|
||||||
"10.0.0.0/24",
|
|
||||||
"192.168.0.1"
|
|
||||||
],
|
|
||||||
"ip_cidr": [
|
|
||||||
"10.0.0.0/24",
|
|
||||||
"192.168.0.1"
|
|
||||||
],
|
|
||||||
"source_port": [
|
|
||||||
12345
|
|
||||||
],
|
|
||||||
"source_port_range": [
|
|
||||||
"1000:2000",
|
|
||||||
":3000",
|
|
||||||
"4000:"
|
|
||||||
],
|
|
||||||
"port": [
|
|
||||||
80,
|
|
||||||
443
|
|
||||||
],
|
|
||||||
"port_range": [
|
|
||||||
"1000:2000",
|
|
||||||
":3000",
|
|
||||||
"4000:"
|
|
||||||
],
|
|
||||||
"invert": false,
|
|
||||||
"action": "direct",
|
|
||||||
"outbound": "wireguard"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "logical",
|
|
||||||
"mode": "and",
|
|
||||||
"rules": [],
|
|
||||||
"invert": false,
|
|
||||||
"action": "direct",
|
|
||||||
"outbound": "wireguard"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! note ""
|
|
||||||
|
|
||||||
当内容只有一项时,可以忽略 JSON 数组 [] 标签。
|
|
||||||
|
|
||||||
### Default Fields
|
|
||||||
|
|
||||||
!!! note ""
|
|
||||||
|
|
||||||
默认规则使用以下匹配逻辑:
|
|
||||||
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr`) &&
|
|
||||||
(`port` || `port_range`) &&
|
|
||||||
(`source_geoip` || `source_ip_cidr`) &&
|
|
||||||
(`source_port` || `source_port_range`) &&
|
|
||||||
`other fields`
|
|
||||||
|
|
||||||
#### inbound
|
|
||||||
|
|
||||||
[入站](/zh/configuration/inbound) 标签。
|
|
||||||
|
|
||||||
#### ip_version
|
|
||||||
|
|
||||||
4 或 6。
|
|
||||||
|
|
||||||
默认不限制。
|
|
||||||
|
|
||||||
#### network
|
|
||||||
|
|
||||||
匹配网络协议。
|
|
||||||
|
|
||||||
可用值:
|
|
||||||
|
|
||||||
* `tcp`
|
|
||||||
* `udp`
|
|
||||||
* `icmpv4`
|
|
||||||
* `icmpv6`
|
|
||||||
|
|
||||||
#### domain
|
|
||||||
|
|
||||||
匹配完整域名。
|
|
||||||
|
|
||||||
#### domain_suffix
|
|
||||||
|
|
||||||
匹配域名后缀。
|
|
||||||
|
|
||||||
#### domain_keyword
|
|
||||||
|
|
||||||
匹配域名关键字。
|
|
||||||
|
|
||||||
#### domain_regex
|
|
||||||
|
|
||||||
匹配域名正则表达式。
|
|
||||||
|
|
||||||
#### geosite
|
|
||||||
|
|
||||||
匹配 GeoSite。
|
|
||||||
|
|
||||||
#### source_geoip
|
|
||||||
|
|
||||||
匹配源 GeoIP。
|
|
||||||
|
|
||||||
#### geoip
|
|
||||||
|
|
||||||
匹配 GeoIP。
|
|
||||||
|
|
||||||
#### source_ip_cidr
|
|
||||||
|
|
||||||
匹配源 IP CIDR。
|
|
||||||
|
|
||||||
#### ip_cidr
|
|
||||||
|
|
||||||
匹配 IP CIDR。
|
|
||||||
|
|
||||||
#### source_port
|
|
||||||
|
|
||||||
匹配源端口。
|
|
||||||
|
|
||||||
#### source_port_range
|
|
||||||
|
|
||||||
匹配源端口范围。
|
|
||||||
|
|
||||||
#### port
|
|
||||||
|
|
||||||
匹配端口。
|
|
||||||
|
|
||||||
#### port_range
|
|
||||||
|
|
||||||
匹配端口范围。
|
|
||||||
|
|
||||||
#### invert
|
|
||||||
|
|
||||||
反选匹配结果。
|
|
||||||
|
|
||||||
#### action
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
| Action | 描述 |
|
|
||||||
|--------|---------------------|
|
|
||||||
| return | 停止 IP 路由并将该连接组装到传输层 |
|
|
||||||
| block | 屏蔽该连接 |
|
|
||||||
| direct | 直接转发该连接 |
|
|
||||||
|
|
||||||
|
|
||||||
#### outbound
|
|
||||||
|
|
||||||
==action 为 direct 则必填==
|
|
||||||
|
|
||||||
目标出站的标签。
|
|
||||||
|
|
||||||
### 逻辑字段
|
|
||||||
|
|
||||||
#### type
|
|
||||||
|
|
||||||
`logical`
|
|
||||||
|
|
||||||
#### mode
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
`and` 或 `or`
|
|
||||||
|
|
||||||
#### rules
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
包括的默认规则。
|
|
||||||
@@ -9,9 +9,7 @@
|
|||||||
"mixed-in"
|
"mixed-in"
|
||||||
],
|
],
|
||||||
"ip_version": 6,
|
"ip_version": 6,
|
||||||
"network": [
|
"network": "tcp",
|
||||||
"tcp"
|
|
||||||
],
|
|
||||||
"auth_user": [
|
"auth_user": [
|
||||||
"usera",
|
"usera",
|
||||||
"userb"
|
"userb"
|
||||||
@@ -246,12 +244,18 @@ Tag of the target outbound.
|
|||||||
|
|
||||||
#### mode
|
#### mode
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
`and` or `or`
|
`and` or `or`
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
|
Included default rules.
|
||||||
|
|
||||||
|
#### invert
|
||||||
|
|
||||||
|
Invert match result.
|
||||||
|
|
||||||
|
#### outbound
|
||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|
||||||
Included default rules.
|
Tag of the target outbound.
|
||||||
|
|||||||
@@ -9,9 +9,7 @@
|
|||||||
"mixed-in"
|
"mixed-in"
|
||||||
],
|
],
|
||||||
"ip_version": 6,
|
"ip_version": 6,
|
||||||
"network": [
|
"network": "tcp",
|
||||||
"tcp"
|
|
||||||
],
|
|
||||||
"auth_user": [
|
"auth_user": [
|
||||||
"usera",
|
"usera",
|
||||||
"userb"
|
"userb"
|
||||||
@@ -244,12 +242,18 @@
|
|||||||
|
|
||||||
#### mode
|
#### mode
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
`and` 或 `or`
|
`and` 或 `or`
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
|
包括的默认规则。
|
||||||
|
|
||||||
|
#### invert
|
||||||
|
|
||||||
|
反选匹配结果。
|
||||||
|
|
||||||
|
#### outbound
|
||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
包括的默认规则。
|
目标出站的标签。
|
||||||
|
|||||||
@@ -8,4 +8,3 @@ Configuration examples for sing-box.
|
|||||||
* [Shadowsocks](./shadowsocks)
|
* [Shadowsocks](./shadowsocks)
|
||||||
* [ShadowTLS](./shadowtls)
|
* [ShadowTLS](./shadowtls)
|
||||||
* [Clash API](./clash-api)
|
* [Clash API](./clash-api)
|
||||||
* [WireGuard Direct](./wireguard-direct)
|
|
||||||
|
|||||||
@@ -8,4 +8,3 @@ sing-box 的配置示例。
|
|||||||
* [Shadowsocks](./shadowsocks)
|
* [Shadowsocks](./shadowsocks)
|
||||||
* [ShadowTLS](./shadowtls)
|
* [ShadowTLS](./shadowtls)
|
||||||
* [Clash API](./clash-api)
|
* [Clash API](./clash-api)
|
||||||
* [WireGuard Direct](./wireguard-direct)
|
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
# WireGuard Direct
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"tag": "google",
|
|
||||||
"address": "tls://8.8.8.8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tag": "local",
|
|
||||||
"address": "223.5.5.5",
|
|
||||||
"detour": "direct"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"geoip": "cn",
|
|
||||||
"server": "direct"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"reverse_mapping": true
|
|
||||||
},
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"type": "tun",
|
|
||||||
"tag": "tun",
|
|
||||||
"inet4_address": "172.19.0.1/30",
|
|
||||||
"auto_route": true,
|
|
||||||
"sniff": true,
|
|
||||||
"stack": "system"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outbounds": [
|
|
||||||
{
|
|
||||||
"type": "wireguard",
|
|
||||||
"tag": "wg",
|
|
||||||
"server": "127.0.0.1",
|
|
||||||
"server_port": 2345,
|
|
||||||
"local_address": [
|
|
||||||
"172.19.0.1/128"
|
|
||||||
],
|
|
||||||
"private_key": "KLTnpPY03pig/WC3zR8U7VWmpANHPFh2/4pwICGJ5Fk=",
|
|
||||||
"peer_public_key": "uvNabcamf6Rs0vzmcw99jsjTJbxo6eWGOykSY66zsUk="
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "dns",
|
|
||||||
"tag": "dns"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "direct",
|
|
||||||
"tag": "direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "block",
|
|
||||||
"tag": "block"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"route": {
|
|
||||||
"ip_rules": [
|
|
||||||
{
|
|
||||||
"port": 53,
|
|
||||||
"action": "return"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"geoip": "cn",
|
|
||||||
"geosite": "cn",
|
|
||||||
"action": "return"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "direct",
|
|
||||||
"outbound": "wg"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"protocol": "dns",
|
|
||||||
"outbound": "dns"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"geoip": "cn",
|
|
||||||
"geosite": "cn",
|
|
||||||
"outbound": "direct"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"auto_detect_interface": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# FakeIP
|
|
||||||
|
|
||||||
FakeIP refers to a type of behavior in a program that simultaneously hijacks both DNS and connection requests. It
|
|
||||||
responds to DNS requests with virtual results and restores mapping when accepting connections.
|
|
||||||
|
|
||||||
#### Advantage
|
|
||||||
|
|
||||||
* Retrieve the requested domain in places like IP routing (L3) where traffic detection is not possible to assist with routing.
|
|
||||||
* Decrease an RTT on the first TCP request to a domain (the most common reason).
|
|
||||||
|
|
||||||
#### Limitation
|
|
||||||
|
|
||||||
* Its mechanism breaks applications that depend on returning correct remote addresses.
|
|
||||||
* Only A and AAAA (IP) requests are supported, which may break applications that rely on other requests.
|
|
||||||
|
|
||||||
#### Recommendation
|
|
||||||
|
|
||||||
* Do not use if you do not need L3 routing.
|
|
||||||
* If using tun, make sure FakeIP ranges is included in the tun's routes.
|
|
||||||
* Enable `experimental.clash_api.store_fakeip` to persist FakeIP records, or use `dns.rules.rewrite_ttl` to avoid losing records after program restart in DNS cached environments.
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# FakeIP
|
|
||||||
|
|
||||||
FakeIP 是指同时劫持 DNS 和连接请求的程序中的一种行为。它通过虚拟结果响应 DNS 请求,在接受连接时恢复映射。
|
|
||||||
|
|
||||||
#### 优点
|
|
||||||
|
|
||||||
* 在像 L3 路由这样无法进行流量探测的地方检索所请求的域名,以协助路由。
|
|
||||||
* 减少对一个域的第一个 TCP 请求的 RTT(这是最常见的原因)。
|
|
||||||
|
|
||||||
#### 限制
|
|
||||||
|
|
||||||
* 它的机制会破坏依赖于返回正确远程地址的应用程序。
|
|
||||||
* 仅支持 A 和 AAAA(IP)请求,这可能会破坏依赖于其他请求的应用程序。
|
|
||||||
|
|
||||||
#### 建议
|
|
||||||
|
|
||||||
* 如果不需要 L3 路由,请勿使用。
|
|
||||||
* 如果使用 tun,请确保 tun 路由中包含 FakeIP 地址范围。
|
|
||||||
* 启用 `experimental.clash_api.store_fakeip` 以持久化 FakeIP 记录,或者使用 `dns.rules.rewrite_ttl` 避免程序重启后在 DNS 被缓存的环境中丢失记录。
|
|
||||||
@@ -11,6 +11,12 @@ it doesn't fit, because it compromises performance or design clarity, or because
|
|||||||
If it bothers you that sing-box is missing feature X, please forgive us and investigate the features that sing-box does
|
If it bothers you that sing-box is missing feature X, please forgive us and investigate the features that sing-box does
|
||||||
have. You might find that they compensate in interesting ways for the lack of X.
|
have. You might find that they compensate in interesting ways for the lack of X.
|
||||||
|
|
||||||
|
#### Fake IP
|
||||||
|
|
||||||
|
Fake IP (also called Fake DNS) is an invasive and imperfect DNS solution that breaks expected behavior, causes DNS leaks
|
||||||
|
and makes many software unusable. It is recommended by some software that lacks DNS processing and caching, but sing-box
|
||||||
|
does not need this.
|
||||||
|
|
||||||
#### Naive outbound
|
#### Naive outbound
|
||||||
|
|
||||||
NaïveProxy's main function is chromium's network stack, and it makes no sense to implement only its transport protocol.
|
NaïveProxy's main function is chromium's network stack, and it makes no sense to implement only its transport protocol.
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
|
|
||||||
如果 sing-box 缺少功能 X 让您感到困扰,请原谅我们并调查 sing-box 确实有的功能。 您可能会发现它们以有趣的方式弥补了 X 的缺失。
|
如果 sing-box 缺少功能 X 让您感到困扰,请原谅我们并调查 sing-box 确实有的功能。 您可能会发现它们以有趣的方式弥补了 X 的缺失。
|
||||||
|
|
||||||
|
#### Fake IP
|
||||||
|
|
||||||
|
Fake IP(也称 Fake DNS)是一种侵入性和不完善的 DNS 解决方案,它打破了预期的行为,导致 DNS 泄漏并使许多软件无法使用。
|
||||||
|
一些缺乏 DNS 处理和缓存的软件推荐使用它,但 sing-box 不需要。
|
||||||
|
|
||||||
#### Naive 出站
|
#### Naive 出站
|
||||||
|
|
||||||
NaïveProxy 的主要功能是 chromium 的网络栈,仅实现它的传输协议是舍本逐末的。
|
NaïveProxy 的主要功能是 chromium 的网络栈,仅实现它的传输协议是舍本逐末的。
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ Experimental Android client for sing-box.
|
|||||||
|
|
||||||
#### Note
|
#### Note
|
||||||
|
|
||||||
* Working directory is at `/sdcard/Android/data/io.nekohasekai.sfa/files` (External files directory)
|
* User Agent in remote profile request is `SFA/$version ($version_code; sing-box $sing_box_version)`
|
||||||
* User Agent is `SFA/$version ($version_code; sing-box $sing_box_version)` in the remote profile request
|
* The working directory is located at `/sdcard/Android/data/io.nekohasekai.sfa/files` (External files directory)
|
||||||
|
* Crash logs is located in `$working_directory/stderr.log`
|
||||||
|
|||||||
@@ -12,5 +12,6 @@
|
|||||||
|
|
||||||
#### 注意事项
|
#### 注意事项
|
||||||
|
|
||||||
* 工作目录位于 `/sdcard/Android/data/io.nekohasekai.sfa/files` (外部文件目录)
|
|
||||||
* 远程配置文件请求中的 User Agent 为 `SFA/$version ($version_code; sing-box $sing_box_version)`
|
* 远程配置文件请求中的 User Agent 为 `SFA/$version ($version_code; sing-box $sing_box_version)`
|
||||||
|
* 工作目录位于 `/sdcard/Android/data/io.nekohasekai.sfa/files` (外部文件目录)
|
||||||
|
* 崩溃日志位于 `$working_directory/stderr.log`
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ Experimental iOS client for sing-box.
|
|||||||
|
|
||||||
#### Note
|
#### Note
|
||||||
|
|
||||||
* `system` tun stack not working on iOS
|
* User Agent in remote profile request is `SFA/$version ($version_code; sing-box $sing_box_version)`
|
||||||
* User Agent is `SFI/$version ($version_code; sing-box $sing_box_version)` in the remote profile request
|
* Crash logs is located in `Settings` -> `View Service Log`
|
||||||
|
|
||||||
#### Privacy policy
|
#### Privacy policy
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
|
|
||||||
#### 注意事项
|
#### 注意事项
|
||||||
|
|
||||||
* `system` tun stack 在 iOS 不工作
|
|
||||||
* 远程配置文件请求中的 User Agent 为 `SFI/$version ($version_code; sing-box $sing_box_version)`
|
* 远程配置文件请求中的 User Agent 为 `SFI/$version ($version_code; sing-box $sing_box_version)`
|
||||||
|
* 崩溃日志位于 `设置` -> `查看服务日志`
|
||||||
|
|
||||||
#### 隐私政策
|
#### 隐私政策
|
||||||
|
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
package clashapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/json"
|
|
||||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
|
||||||
"github.com/sagernet/websocket"
|
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
|
||||||
"github.com/go-chi/render"
|
|
||||||
)
|
|
||||||
|
|
||||||
// API created by Clash.Meta
|
|
||||||
|
|
||||||
func (s *Server) setupMetaAPI(r chi.Router) {
|
|
||||||
r.Get("/memory", memory(s.trafficManager))
|
|
||||||
r.Mount("/group", groupRouter(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
type Memory struct {
|
|
||||||
Inuse uint64 `json:"inuse"`
|
|
||||||
OSLimit uint64 `json:"oslimit"` // maybe we need it in the future
|
|
||||||
}
|
|
||||||
|
|
||||||
func memory(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var wsConn *websocket.Conn
|
|
||||||
if websocket.IsWebSocketUpgrade(r) {
|
|
||||||
var err error
|
|
||||||
wsConn, err = upgrader.Upgrade(w, r, nil)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if wsConn == nil {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
render.Status(r, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
tick := time.NewTicker(time.Second)
|
|
||||||
defer tick.Stop()
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
var err error
|
|
||||||
first := true
|
|
||||||
for range tick.C {
|
|
||||||
buf.Reset()
|
|
||||||
|
|
||||||
inuse := trafficManager.Snapshot().Memory
|
|
||||||
|
|
||||||
// make chat.js begin with zero
|
|
||||||
// this is shit var,but we need output 0 for first time
|
|
||||||
if first {
|
|
||||||
first = false
|
|
||||||
inuse = 0
|
|
||||||
}
|
|
||||||
if err := json.NewEncoder(buf).Encode(Memory{
|
|
||||||
Inuse: inuse,
|
|
||||||
OSLimit: 0,
|
|
||||||
}); err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if wsConn == nil {
|
|
||||||
_, err = w.Write(buf.Bytes())
|
|
||||||
w.(http.Flusher).Flush()
|
|
||||||
} else {
|
|
||||||
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
package clashapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/badjson"
|
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
|
||||||
"github.com/sagernet/sing-box/outbound"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/batch"
|
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
|
||||||
"github.com/go-chi/render"
|
|
||||||
)
|
|
||||||
|
|
||||||
func groupRouter(server *Server) http.Handler {
|
|
||||||
r := chi.NewRouter()
|
|
||||||
r.Get("/", getGroups(server))
|
|
||||||
r.Route("/{name}", func(r chi.Router) {
|
|
||||||
r.Use(parseProxyName, findProxyByName(server.router))
|
|
||||||
r.Get("/", getGroup(server))
|
|
||||||
r.Get("/delay", getGroupDelay(server))
|
|
||||||
})
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func getGroups(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
groups := common.Map(common.Filter(server.router.Outbounds(), func(it adapter.Outbound) bool {
|
|
||||||
_, isGroup := it.(adapter.OutboundGroup)
|
|
||||||
return isGroup
|
|
||||||
}), func(it adapter.Outbound) *badjson.JSONObject {
|
|
||||||
return proxyInfo(server, it)
|
|
||||||
})
|
|
||||||
render.JSON(w, r, render.M{
|
|
||||||
"proxies": groups,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getGroup(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
|
|
||||||
if _, ok := proxy.(adapter.OutboundGroup); ok {
|
|
||||||
render.JSON(w, r, proxyInfo(server, proxy))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
render.Status(r, http.StatusNotFound)
|
|
||||||
render.JSON(w, r, ErrNotFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
|
|
||||||
group, ok := proxy.(adapter.OutboundGroup)
|
|
||||||
if !ok {
|
|
||||||
render.Status(r, http.StatusNotFound)
|
|
||||||
render.JSON(w, r, ErrNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
query := r.URL.Query()
|
|
||||||
url := query.Get("url")
|
|
||||||
if strings.HasPrefix(url, "http://") {
|
|
||||||
url = ""
|
|
||||||
}
|
|
||||||
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
render.Status(r, http.StatusBadRequest)
|
|
||||||
render.JSON(w, r, ErrBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(r.Context(), time.Millisecond*time.Duration(timeout))
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
var result map[string]uint16
|
|
||||||
if urlTestGroup, isURLTestGroup := group.(adapter.URLTestGroup); isURLTestGroup {
|
|
||||||
result, err = urlTestGroup.URLTest(ctx, url)
|
|
||||||
} else {
|
|
||||||
outbounds := common.FilterNotNil(common.Map(group.All(), func(it string) adapter.Outbound {
|
|
||||||
itOutbound, _ := server.router.Outbound(it)
|
|
||||||
return itOutbound
|
|
||||||
}))
|
|
||||||
b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10))
|
|
||||||
checked := make(map[string]bool)
|
|
||||||
result = make(map[string]uint16)
|
|
||||||
var resultAccess sync.Mutex
|
|
||||||
for _, detour := range outbounds {
|
|
||||||
tag := detour.Tag()
|
|
||||||
realTag := outbound.RealTag(detour)
|
|
||||||
if checked[realTag] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
checked[realTag] = true
|
|
||||||
p, loaded := server.router.Outbound(realTag)
|
|
||||||
if !loaded {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
b.Go(realTag, func() (any, error) {
|
|
||||||
t, err := urltest.URLTest(ctx, url, p)
|
|
||||||
if err != nil {
|
|
||||||
server.logger.Debug("outbound ", tag, " unavailable: ", err)
|
|
||||||
server.urlTestHistory.DeleteURLTestHistory(realTag)
|
|
||||||
} else {
|
|
||||||
server.logger.Debug("outbound ", tag, " available: ", t, "ms")
|
|
||||||
server.urlTestHistory.StoreURLTestHistory(realTag, &urltest.History{
|
|
||||||
Time: time.Now(),
|
|
||||||
Delay: t,
|
|
||||||
})
|
|
||||||
resultAccess.Lock()
|
|
||||||
result[tag] = t
|
|
||||||
resultAccess.Unlock()
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
b.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
render.Status(r, http.StatusGatewayTimeout)
|
|
||||||
render.JSON(w, r, newError(err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
render.JSON(w, r, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,28 +3,21 @@ package clashapi
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
func cacheRouter(router adapter.Router) http.Handler {
|
func cacheRouter() http.Handler {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Post("/fakeip/flush", flushFakeip(router))
|
r.Post("/fakeip/flush", flushFakeip)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func flushFakeip(router adapter.Router) func(w http.ResponseWriter, r *http.Request) {
|
func flushFakeip(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
/*if err := cachefile.Cache().FlushFakeip(); err != nil {
|
||||||
if cacheFile := router.ClashServer().CacheFile(); cacheFile != nil {
|
render.Status(r, http.StatusInternalServerError)
|
||||||
err := cacheFile.FakeIPReset()
|
render.JSON(w, r, newError(err.Error()))
|
||||||
if err != nil {
|
return
|
||||||
render.Status(r, http.StatusInternalServerError)
|
}*/
|
||||||
render.JSON(w, r, newError(err.Error()))
|
render.NoContent(w, r)
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render.NoContent(w, r)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
package cachefile
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
|
|
||||||
"go.etcd.io/bbolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
bucketFakeIP = []byte("fakeip")
|
|
||||||
keyMetadata = []byte("metadata")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *CacheFile) FakeIPMetadata() *adapter.FakeIPMetadata {
|
|
||||||
var metadata adapter.FakeIPMetadata
|
|
||||||
err := c.DB.View(func(tx *bbolt.Tx) error {
|
|
||||||
bucket := tx.Bucket(bucketFakeIP)
|
|
||||||
if bucket == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
metadataBinary := bucket.Get(keyMetadata)
|
|
||||||
if len(metadataBinary) == 0 {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
return metadata.UnmarshalBinary(metadataBinary)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) FakeIPSaveMetadata(metadata *adapter.FakeIPMetadata) error {
|
|
||||||
return c.DB.Batch(func(tx *bbolt.Tx) error {
|
|
||||||
bucket, err := tx.CreateBucketIfNotExists(bucketFakeIP)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
metadataBinary, err := metadata.MarshalBinary()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return bucket.Put(keyMetadata, metadataBinary)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) FakeIPStore(address netip.Addr, domain string) error {
|
|
||||||
return c.DB.Batch(func(tx *bbolt.Tx) error {
|
|
||||||
bucket, err := tx.CreateBucketIfNotExists(bucketFakeIP)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return bucket.Put(address.AsSlice(), []byte(domain))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) FakeIPLoad(address netip.Addr) (string, bool) {
|
|
||||||
var domain string
|
|
||||||
_ = c.DB.View(func(tx *bbolt.Tx) error {
|
|
||||||
bucket := tx.Bucket(bucketFakeIP)
|
|
||||||
if bucket == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
domain = string(bucket.Get(address.AsSlice()))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return domain, domain != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) FakeIPReset() error {
|
|
||||||
return c.DB.Batch(func(tx *bbolt.Tx) error {
|
|
||||||
return tx.DeleteBucket(bucketFakeIP)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -215,9 +214,6 @@ func getProxyDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
|||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
query := r.URL.Query()
|
query := r.URL.Query()
|
||||||
url := query.Get("url")
|
url := query.Get("url")
|
||||||
if strings.HasPrefix(url, "http://") {
|
|
||||||
url = ""
|
|
||||||
}
|
|
||||||
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
|
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Status(r, http.StatusBadRequest)
|
render.Status(r, http.StatusBadRequest)
|
||||||
|
|||||||
@@ -44,13 +44,8 @@ type Server struct {
|
|||||||
urlTestHistory *urltest.HistoryStorage
|
urlTestHistory *urltest.HistoryStorage
|
||||||
mode string
|
mode string
|
||||||
storeSelected bool
|
storeSelected bool
|
||||||
storeFakeIP bool
|
|
||||||
cacheFilePath string
|
cacheFilePath string
|
||||||
cacheFile adapter.ClashCacheFile
|
cacheFile adapter.ClashCacheFile
|
||||||
|
|
||||||
externalUI string
|
|
||||||
externalUIDownloadURL string
|
|
||||||
externalUIDownloadDetour string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
|
func NewServer(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
|
||||||
@@ -63,18 +58,15 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
|
|||||||
Addr: options.ExternalController,
|
Addr: options.ExternalController,
|
||||||
Handler: chiRouter,
|
Handler: chiRouter,
|
||||||
},
|
},
|
||||||
trafficManager: trafficManager,
|
trafficManager: trafficManager,
|
||||||
urlTestHistory: urltest.NewHistoryStorage(),
|
urlTestHistory: urltest.NewHistoryStorage(),
|
||||||
mode: strings.ToLower(options.DefaultMode),
|
mode: strings.ToLower(options.DefaultMode),
|
||||||
storeSelected: options.StoreSelected,
|
|
||||||
storeFakeIP: options.StoreFakeIP,
|
|
||||||
externalUIDownloadURL: options.ExternalUIDownloadURL,
|
|
||||||
externalUIDownloadDetour: options.ExternalUIDownloadDetour,
|
|
||||||
}
|
}
|
||||||
if server.mode == "" {
|
if server.mode == "" {
|
||||||
server.mode = "rule"
|
server.mode = "rule"
|
||||||
}
|
}
|
||||||
if options.StoreSelected || options.StoreFakeIP {
|
if options.StoreSelected {
|
||||||
|
server.storeSelected = true
|
||||||
cachePath := os.ExpandEnv(options.CacheFile)
|
cachePath := os.ExpandEnv(options.CacheFile)
|
||||||
if cachePath == "" {
|
if cachePath == "" {
|
||||||
cachePath = "cache.db"
|
cachePath = "cache.db"
|
||||||
@@ -107,15 +99,12 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
|
|||||||
r.Mount("/providers/rules", ruleProviderRouter())
|
r.Mount("/providers/rules", ruleProviderRouter())
|
||||||
r.Mount("/script", scriptRouter())
|
r.Mount("/script", scriptRouter())
|
||||||
r.Mount("/profile", profileRouter())
|
r.Mount("/profile", profileRouter())
|
||||||
r.Mount("/cache", cacheRouter(router))
|
r.Mount("/cache", cacheRouter())
|
||||||
r.Mount("/dns", dnsRouter(router))
|
r.Mount("/dns", dnsRouter(router))
|
||||||
|
|
||||||
server.setupMetaAPI(r)
|
|
||||||
})
|
})
|
||||||
if options.ExternalUI != "" {
|
if options.ExternalUI != "" {
|
||||||
server.externalUI = C.BasePath(os.ExpandEnv(options.ExternalUI))
|
|
||||||
chiRouter.Group(func(r chi.Router) {
|
chiRouter.Group(func(r chi.Router) {
|
||||||
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(server.externalUI)))
|
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(C.BasePath(os.ExpandEnv(options.ExternalUI)))))
|
||||||
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
|
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
|
||||||
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
|
||||||
fs.ServeHTTP(w, r)
|
fs.ServeHTTP(w, r)
|
||||||
@@ -137,7 +126,6 @@ func (s *Server) PreStart() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Start() error {
|
func (s *Server) Start() error {
|
||||||
s.checkAndDownloadExternalUI()
|
|
||||||
listener, err := net.Listen("tcp", s.httpServer.Addr)
|
listener, err := net.Listen("tcp", s.httpServer.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "external controller listen error")
|
return E.Cause(err, "external controller listen error")
|
||||||
@@ -168,10 +156,6 @@ func (s *Server) StoreSelected() bool {
|
|||||||
return s.storeSelected
|
return s.storeSelected
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) StoreFakeIP() bool {
|
|
||||||
return s.storeFakeIP
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) CacheFile() adapter.ClashCacheFile {
|
func (s *Server) CacheFile() adapter.ClashCacheFile {
|
||||||
return s.cacheFile
|
return s.cacheFile
|
||||||
}
|
}
|
||||||
@@ -408,5 +392,5 @@ func getLogs(logFactory log.ObservableFactory) func(w http.ResponseWriter, r *ht
|
|||||||
}
|
}
|
||||||
|
|
||||||
func version(w http.ResponseWriter, r *http.Request) {
|
func version(w http.ResponseWriter, r *http.Request) {
|
||||||
render.JSON(w, r, render.M{"version": "sing-box " + C.Version, "premium": true, "meta": true})
|
render.JSON(w, r, render.M{"version": "sing-box " + C.Version, "premium": true})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,164 +0,0 @@
|
|||||||
package clashapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/zip"
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Server) checkAndDownloadExternalUI() {
|
|
||||||
if s.externalUI == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
entries, err := os.ReadDir(s.externalUI)
|
|
||||||
if err != nil {
|
|
||||||
os.MkdirAll(s.externalUI, 0o755)
|
|
||||||
}
|
|
||||||
if len(entries) == 0 {
|
|
||||||
err = s.downloadExternalUI()
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("download external ui error: ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) downloadExternalUI() error {
|
|
||||||
var downloadURL string
|
|
||||||
if s.externalUIDownloadURL != "" {
|
|
||||||
downloadURL = s.externalUIDownloadURL
|
|
||||||
} else {
|
|
||||||
downloadURL = "https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip"
|
|
||||||
}
|
|
||||||
s.logger.Info("downloading external ui")
|
|
||||||
var detour adapter.Outbound
|
|
||||||
if s.externalUIDownloadDetour != "" {
|
|
||||||
outbound, loaded := s.router.Outbound(s.externalUIDownloadDetour)
|
|
||||||
if !loaded {
|
|
||||||
return E.New("detour outbound not found: ", s.externalUIDownloadDetour)
|
|
||||||
}
|
|
||||||
detour = outbound
|
|
||||||
} else {
|
|
||||||
detour = s.router.DefaultOutbound(N.NetworkTCP)
|
|
||||||
}
|
|
||||||
httpClient := &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
TLSHandshakeTimeout: 5 * time.Second,
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
defer httpClient.CloseIdleConnections()
|
|
||||||
response, err := httpClient.Get(downloadURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
if response.StatusCode != http.StatusOK {
|
|
||||||
return E.New("download external ui failed: ", response.Status)
|
|
||||||
}
|
|
||||||
err = s.downloadZIP(filepath.Base(downloadURL), response.Body, s.externalUI)
|
|
||||||
if err != nil {
|
|
||||||
removeAllInDirectory(s.externalUI)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) downloadZIP(name string, body io.Reader, output string) error {
|
|
||||||
tempFile, err := C.CreateTemp(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer os.Remove(tempFile.Name())
|
|
||||||
_, err = io.Copy(tempFile, body)
|
|
||||||
tempFile.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
reader, err := zip.OpenReader(tempFile.Name())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer reader.Close()
|
|
||||||
trimDir := zipIsInSingleDirectory(reader.File)
|
|
||||||
for _, file := range reader.File {
|
|
||||||
if file.FileInfo().IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pathElements := strings.Split(file.Name, "/")
|
|
||||||
if trimDir {
|
|
||||||
pathElements = pathElements[1:]
|
|
||||||
}
|
|
||||||
saveDirectory := output
|
|
||||||
if len(pathElements) > 1 {
|
|
||||||
saveDirectory = filepath.Join(saveDirectory, filepath.Join(pathElements[:len(pathElements)-1]...))
|
|
||||||
}
|
|
||||||
err = os.MkdirAll(saveDirectory, 0o755)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
savePath := filepath.Join(saveDirectory, pathElements[len(pathElements)-1])
|
|
||||||
err = downloadZIPEntry(file, savePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func downloadZIPEntry(zipFile *zip.File, savePath string) error {
|
|
||||||
saveFile, err := os.Create(savePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer saveFile.Close()
|
|
||||||
reader, err := zipFile.Open()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer reader.Close()
|
|
||||||
return common.Error(io.Copy(saveFile, reader))
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeAllInDirectory(directory string) {
|
|
||||||
dirEntries, err := os.ReadDir(directory)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, dirEntry := range dirEntries {
|
|
||||||
os.RemoveAll(filepath.Join(directory, dirEntry.Name()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func zipIsInSingleDirectory(files []*zip.File) bool {
|
|
||||||
var singleDirectory string
|
|
||||||
for _, file := range files {
|
|
||||||
if file.FileInfo().IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pathElements := strings.Split(file.Name, "/")
|
|
||||||
if len(pathElements) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if singleDirectory == "" {
|
|
||||||
singleDirectory = pathElements[0]
|
|
||||||
} else if singleDirectory != pathElements[0] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package trafficontrol
|
package trafficontrol
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/experimental/clashapi/compatible"
|
"github.com/sagernet/sing-box/experimental/clashapi/compatible"
|
||||||
@@ -19,15 +18,12 @@ type Manager struct {
|
|||||||
connections compatible.Map[string, tracker]
|
connections compatible.Map[string, tracker]
|
||||||
ticker *time.Ticker
|
ticker *time.Ticker
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
// process *process.Process
|
|
||||||
memory uint64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager() *Manager {
|
func NewManager() *Manager {
|
||||||
manager := &Manager{
|
manager := &Manager{
|
||||||
ticker: time.NewTicker(time.Second),
|
ticker: time.NewTicker(time.Second),
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
// process: &process.Process{Pid: int32(os.Getpid())},
|
|
||||||
}
|
}
|
||||||
go manager.handle()
|
go manager.handle()
|
||||||
return manager
|
return manager
|
||||||
@@ -62,18 +58,10 @@ func (m *Manager) Snapshot() *Snapshot {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
//if memoryInfo, err := m.process.MemoryInfo(); err == nil {
|
|
||||||
// m.memory = memoryInfo.RSS
|
|
||||||
//} else {
|
|
||||||
var memStats runtime.MemStats
|
|
||||||
runtime.ReadMemStats(&memStats)
|
|
||||||
m.memory = memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased
|
|
||||||
|
|
||||||
return &Snapshot{
|
return &Snapshot{
|
||||||
UploadTotal: m.uploadTotal.Load(),
|
UploadTotal: m.uploadTotal.Load(),
|
||||||
DownloadTotal: m.downloadTotal.Load(),
|
DownloadTotal: m.downloadTotal.Load(),
|
||||||
Connections: connections,
|
Connections: connections,
|
||||||
Memory: m.memory,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,5 +100,4 @@ type Snapshot struct {
|
|||||||
DownloadTotal int64 `json:"downloadTotal"`
|
DownloadTotal int64 `json:"downloadTotal"`
|
||||||
UploadTotal int64 `json:"uploadTotal"`
|
UploadTotal int64 `json:"uploadTotal"`
|
||||||
Connections []tracker `json:"connections"`
|
Connections []tracker `json:"connections"`
|
||||||
Memory uint64 `json:"memory"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,3 +29,19 @@ func (i *iterator[T]) Next() T {
|
|||||||
func (i *iterator[T]) HasNext() bool {
|
func (i *iterator[T]) HasNext() bool {
|
||||||
return len(i.values) > 0
|
return len(i.values) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type abstractIterator[T any] interface {
|
||||||
|
Next() T
|
||||||
|
HasNext() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func iteratorToArray[T any](iterator abstractIterator[T]) []T {
|
||||||
|
if iterator == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var values []T
|
||||||
|
for iterator.HasNext() {
|
||||||
|
values = append(values, iterator.Next())
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|||||||
183
experimental/libbox/monitor.go
Normal file
183
experimental/libbox/monitor.go
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
package libbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ tun.DefaultInterfaceMonitor = (*platformDefaultInterfaceMonitor)(nil)
|
||||||
|
_ InterfaceUpdateListener = (*platformDefaultInterfaceMonitor)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type platformDefaultInterfaceMonitor struct {
|
||||||
|
*platformInterfaceWrapper
|
||||||
|
errorHandler E.Handler
|
||||||
|
networkAddresses []networkAddress
|
||||||
|
defaultInterfaceName string
|
||||||
|
defaultInterfaceIndex int
|
||||||
|
element *list.Element[tun.NetworkUpdateCallback]
|
||||||
|
access sync.Mutex
|
||||||
|
callbacks list.List[tun.DefaultInterfaceUpdateCallback]
|
||||||
|
}
|
||||||
|
|
||||||
|
type networkAddress struct {
|
||||||
|
interfaceName string
|
||||||
|
interfaceIndex int
|
||||||
|
addresses []netip.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *platformDefaultInterfaceMonitor) Start() error {
|
||||||
|
return m.iif.StartDefaultInterfaceMonitor(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *platformDefaultInterfaceMonitor) Close() error {
|
||||||
|
return m.iif.CloseDefaultInterfaceMonitor(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *platformDefaultInterfaceMonitor) DefaultInterfaceName(destination netip.Addr) string {
|
||||||
|
for _, address := range m.networkAddresses {
|
||||||
|
for _, prefix := range address.addresses {
|
||||||
|
if prefix.Contains(destination) {
|
||||||
|
return address.interfaceName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m.defaultInterfaceName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *platformDefaultInterfaceMonitor) DefaultInterfaceIndex(destination netip.Addr) int {
|
||||||
|
for _, address := range m.networkAddresses {
|
||||||
|
for _, prefix := range address.addresses {
|
||||||
|
if prefix.Contains(destination) {
|
||||||
|
return address.interfaceIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m.defaultInterfaceIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *platformDefaultInterfaceMonitor) OverrideAndroidVPN() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *platformDefaultInterfaceMonitor) AndroidVPNEnabled() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *platformDefaultInterfaceMonitor) RegisterCallback(callback tun.DefaultInterfaceUpdateCallback) *list.Element[tun.DefaultInterfaceUpdateCallback] {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
return m.callbacks.PushBack(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
m.callbacks.Remove(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32) {
|
||||||
|
var err error
|
||||||
|
if m.iif.UsePlatformInterfaceGetter() {
|
||||||
|
err = m.updateInterfacesPlatform()
|
||||||
|
} else {
|
||||||
|
err = m.updateInterfaces()
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
err = m.router.UpdateInterfaces()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
m.errorHandler.NewError(context.Background(), E.Cause(err, "update interfaces"))
|
||||||
|
}
|
||||||
|
interfaceIndex := int(interfaceIndex32)
|
||||||
|
if interfaceName == "" {
|
||||||
|
for _, netIf := range m.networkAddresses {
|
||||||
|
if netIf.interfaceIndex == interfaceIndex {
|
||||||
|
interfaceName = netIf.interfaceName
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if interfaceIndex == -1 {
|
||||||
|
for _, netIf := range m.networkAddresses {
|
||||||
|
if netIf.interfaceName == interfaceName {
|
||||||
|
interfaceIndex = netIf.interfaceIndex
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if interfaceName == "" {
|
||||||
|
m.errorHandler.NewError(context.Background(), E.New("invalid interface name for ", interfaceIndex))
|
||||||
|
return
|
||||||
|
} else if interfaceIndex == -1 {
|
||||||
|
m.errorHandler.NewError(context.Background(), E.New("invalid interface index for ", interfaceName))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if m.defaultInterfaceName == interfaceName && m.defaultInterfaceIndex == interfaceIndex {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.defaultInterfaceName = interfaceName
|
||||||
|
m.defaultInterfaceIndex = interfaceIndex
|
||||||
|
m.access.Lock()
|
||||||
|
callbacks := m.callbacks.Array()
|
||||||
|
m.access.Unlock()
|
||||||
|
for _, callback := range callbacks {
|
||||||
|
err = callback(tun.EventInterfaceUpdate)
|
||||||
|
if err != nil {
|
||||||
|
m.errorHandler.NewError(context.Background(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *platformDefaultInterfaceMonitor) updateInterfaces() error {
|
||||||
|
interfaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var addresses []networkAddress
|
||||||
|
for _, iif := range interfaces {
|
||||||
|
var netAddresses []net.Addr
|
||||||
|
netAddresses, err = iif.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var address networkAddress
|
||||||
|
address.interfaceName = iif.Name
|
||||||
|
address.interfaceIndex = iif.Index
|
||||||
|
address.addresses = common.Map(common.FilterIsInstance(netAddresses, func(it net.Addr) (*net.IPNet, bool) {
|
||||||
|
value, loaded := it.(*net.IPNet)
|
||||||
|
return value, loaded
|
||||||
|
}), func(it *net.IPNet) netip.Prefix {
|
||||||
|
bits, _ := it.Mask.Size()
|
||||||
|
return netip.PrefixFrom(M.AddrFromIP(it.IP), bits)
|
||||||
|
})
|
||||||
|
addresses = append(addresses, address)
|
||||||
|
}
|
||||||
|
m.networkAddresses = addresses
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *platformDefaultInterfaceMonitor) updateInterfacesPlatform() error {
|
||||||
|
interfaces, err := m.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var addresses []networkAddress
|
||||||
|
for _, iif := range interfaces {
|
||||||
|
var address networkAddress
|
||||||
|
address.interfaceName = iif.Name
|
||||||
|
address.interfaceIndex = iif.Index
|
||||||
|
// address.addresses = common.Map(iif.Addresses, netip.MustParsePrefix)
|
||||||
|
addresses = append(addresses, address)
|
||||||
|
}
|
||||||
|
m.networkAddresses = addresses
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
package libbox
|
package libbox
|
||||||
|
|
||||||
import "github.com/sagernet/sing-box/option"
|
import (
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
type PlatformInterface interface {
|
type PlatformInterface interface {
|
||||||
|
UsePlatformAutoDetectInterfaceControl() bool
|
||||||
AutoDetectInterfaceControl(fd int32) error
|
AutoDetectInterfaceControl(fd int32) error
|
||||||
OpenTun(options TunOptions) (int32, error)
|
OpenTun(options TunOptions) (int32, error)
|
||||||
WriteLog(message string)
|
WriteLog(message string)
|
||||||
@@ -10,6 +13,11 @@ type PlatformInterface interface {
|
|||||||
FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error)
|
FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error)
|
||||||
PackageNameByUid(uid int32) (string, error)
|
PackageNameByUid(uid int32) (string, error)
|
||||||
UIDByPackageName(packageName string) (int32, error)
|
UIDByPackageName(packageName string) (int32, error)
|
||||||
|
UsePlatformDefaultInterfaceMonitor() bool
|
||||||
|
StartDefaultInterfaceMonitor(listener InterfaceUpdateListener) error
|
||||||
|
CloseDefaultInterfaceMonitor(listener InterfaceUpdateListener) error
|
||||||
|
UsePlatformInterfaceGetter() bool
|
||||||
|
GetInterfaces() (NetworkInterfaceIterator, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type TunInterface interface {
|
type TunInterface interface {
|
||||||
@@ -17,8 +25,19 @@ type TunInterface interface {
|
|||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type OnDemandRuleIterator interface {
|
type InterfaceUpdateListener interface {
|
||||||
Next() OnDemandRule
|
UpdateDefaultInterface(interfaceName string, interfaceIndex int32)
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkInterface struct {
|
||||||
|
Index int32
|
||||||
|
MTU int32
|
||||||
|
Name string
|
||||||
|
Addresses StringIterator
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkInterfaceIterator interface {
|
||||||
|
Next() *NetworkInterface
|
||||||
HasNext() bool
|
HasNext() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,6 +50,11 @@ type OnDemandRule interface {
|
|||||||
ProbeURL() string
|
ProbeURL() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OnDemandRuleIterator interface {
|
||||||
|
Next() OnDemandRule
|
||||||
|
HasNext() bool
|
||||||
|
}
|
||||||
|
|
||||||
type onDemandRule struct {
|
type onDemandRule struct {
|
||||||
option.OnDemandRule
|
option.OnDemandRule
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,34 @@
|
|||||||
package platform
|
package platform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/process"
|
"github.com/sagernet/sing-box/common/process"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Interface interface {
|
type Interface interface {
|
||||||
|
Initialize(ctx context.Context, router adapter.Router) error
|
||||||
|
UsePlatformAutoDetectInterfaceControl() bool
|
||||||
AutoDetectInterfaceControl() control.Func
|
AutoDetectInterfaceControl() control.Func
|
||||||
OpenTun(options tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
||||||
|
UsePlatformDefaultInterfaceMonitor() bool
|
||||||
|
CreateDefaultInterfaceMonitor(errorHandler E.Handler) tun.DefaultInterfaceMonitor
|
||||||
|
UsePlatformInterfaceGetter() bool
|
||||||
|
Interfaces() ([]NetworkInterface, error)
|
||||||
process.Searcher
|
process.Searcher
|
||||||
io.Writer
|
io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NetworkInterface struct {
|
||||||
|
Index int
|
||||||
|
MTU int
|
||||||
|
Name string
|
||||||
|
Addresses []netip.Prefix
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/process"
|
"github.com/sagernet/sing-box/common/process"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
|
"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@@ -31,7 +33,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
|
|||||||
instance, err := box.New(box.Options{
|
instance, err := box.New(box.Options{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Options: options,
|
Options: options,
|
||||||
PlatformInterface: &platformInterfaceWrapper{platformInterface, platformInterface.UseProcFS()},
|
PlatformInterface: &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
@@ -58,6 +60,16 @@ var _ platform.Interface = (*platformInterfaceWrapper)(nil)
|
|||||||
type platformInterfaceWrapper struct {
|
type platformInterfaceWrapper struct {
|
||||||
iif PlatformInterface
|
iif PlatformInterface
|
||||||
useProcFS bool
|
useProcFS bool
|
||||||
|
router adapter.Router
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *platformInterfaceWrapper) Initialize(ctx context.Context, router adapter.Router) error {
|
||||||
|
w.router = router
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *platformInterfaceWrapper) UsePlatformAutoDetectInterfaceControl() bool {
|
||||||
|
return w.iif.UsePlatformAutoDetectInterfaceControl()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) AutoDetectInterfaceControl() control.Func {
|
func (w *platformInterfaceWrapper) AutoDetectInterfaceControl() control.Func {
|
||||||
@@ -68,7 +80,7 @@ func (w *platformInterfaceWrapper) AutoDetectInterfaceControl() control.Func {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) OpenTun(options tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
|
func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
|
||||||
if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 {
|
if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 {
|
||||||
return nil, E.New("android: unsupported uid options")
|
return nil, E.New("android: unsupported uid options")
|
||||||
}
|
}
|
||||||
@@ -79,12 +91,16 @@ func (w *platformInterfaceWrapper) OpenTun(options tun.Options, platformOptions
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
options.Name, err = getTunnelName(tunFd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "query tun name")
|
||||||
|
}
|
||||||
dupFd, err := dup(int(tunFd))
|
dupFd, err := dup(int(tunFd))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "dup tun file descriptor")
|
return nil, E.Cause(err, "dup tun file descriptor")
|
||||||
}
|
}
|
||||||
options.FileDescriptor = dupFd
|
options.FileDescriptor = dupFd
|
||||||
return tun.New(options)
|
return tun.New(*options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) Write(p []byte) (n int, err error) {
|
func (w *platformInterfaceWrapper) Write(p []byte) (n int, err error) {
|
||||||
@@ -118,3 +134,36 @@ func (w *platformInterfaceWrapper) FindProcessInfo(ctx context.Context, network
|
|||||||
packageName, _ := w.iif.PackageNameByUid(uid)
|
packageName, _ := w.iif.PackageNameByUid(uid)
|
||||||
return &process.Info{UserId: uid, PackageName: packageName}, nil
|
return &process.Info{UserId: uid, PackageName: packageName}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *platformInterfaceWrapper) UsePlatformDefaultInterfaceMonitor() bool {
|
||||||
|
return w.iif.UsePlatformDefaultInterfaceMonitor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(errorHandler E.Handler) tun.DefaultInterfaceMonitor {
|
||||||
|
return &platformDefaultInterfaceMonitor{
|
||||||
|
platformInterfaceWrapper: w,
|
||||||
|
errorHandler: errorHandler,
|
||||||
|
defaultInterfaceIndex: -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *platformInterfaceWrapper) UsePlatformInterfaceGetter() bool {
|
||||||
|
return w.iif.UsePlatformInterfaceGetter()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *platformInterfaceWrapper) Interfaces() ([]platform.NetworkInterface, error) {
|
||||||
|
interfaceIterator, err := w.iif.GetInterfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var interfaces []platform.NetworkInterface
|
||||||
|
for _, netInterface := range iteratorToArray[*NetworkInterface](interfaceIterator) {
|
||||||
|
interfaces = append(interfaces, platform.NetworkInterface{
|
||||||
|
Index: int(netInterface.Index),
|
||||||
|
MTU: int(netInterface.MTU),
|
||||||
|
Name: netInterface.Name,
|
||||||
|
Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return interfaces, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,10 +10,6 @@ func SetBasePath(path string) {
|
|||||||
C.SetBasePath(path)
|
C.SetBasePath(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetTempPath(path string) {
|
|
||||||
C.SetTempPath(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Version() string {
|
func Version() string {
|
||||||
return C.Version
|
return C.Version
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func mapRoutePrefix(prefixes []netip.Prefix) RoutePrefixIterator {
|
|||||||
var _ TunOptions = (*tunOptions)(nil)
|
var _ TunOptions = (*tunOptions)(nil)
|
||||||
|
|
||||||
type tunOptions struct {
|
type tunOptions struct {
|
||||||
tun.Options
|
*tun.Options
|
||||||
option.TunPlatformOptions
|
option.TunPlatformOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
experimental/libbox/tun_name_darwin.go
Normal file
11
experimental/libbox/tun_name_darwin.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package libbox
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
func getTunnelName(fd int32) (string, error) {
|
||||||
|
return unix.GetsockoptString(
|
||||||
|
int(fd),
|
||||||
|
2, /* #define SYSPROTO_CONTROL 2 */
|
||||||
|
2, /* #define UTUN_OPT_IFNAME 2 */
|
||||||
|
)
|
||||||
|
}
|
||||||
26
experimental/libbox/tun_name_linux.go
Normal file
26
experimental/libbox/tun_name_linux.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package libbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ifReqSize = unix.IFNAMSIZ + 64
|
||||||
|
|
||||||
|
func getTunnelName(fd int32) (string, error) {
|
||||||
|
var ifr [ifReqSize]byte
|
||||||
|
var errno syscall.Errno
|
||||||
|
_, _, errno = unix.Syscall(
|
||||||
|
unix.SYS_IOCTL,
|
||||||
|
uintptr(fd),
|
||||||
|
uintptr(unix.TUNGETIFF),
|
||||||
|
uintptr(unsafe.Pointer(&ifr[0])),
|
||||||
|
)
|
||||||
|
if errno != 0 {
|
||||||
|
return "", fmt.Errorf("failed to get name of TUN device: %w", errno)
|
||||||
|
}
|
||||||
|
return unix.ByteSliceToString(ifr[:]), nil
|
||||||
|
}
|
||||||
9
experimental/libbox/tun_name_other.go
Normal file
9
experimental/libbox/tun_name_other.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
//go:build !(darwin || linux)
|
||||||
|
|
||||||
|
package libbox
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
func getTunnelName(fd int32) (string, error) {
|
||||||
|
return "", os.ErrInvalid
|
||||||
|
}
|
||||||
10
go.mod
10
go.mod
@@ -25,12 +25,12 @@ require (
|
|||||||
github.com/sagernet/gomobile v0.0.0-20230413023804-244d7ff07035
|
github.com/sagernet/gomobile v0.0.0-20230413023804-244d7ff07035
|
||||||
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32
|
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||||
github.com/sagernet/sing v0.2.3
|
github.com/sagernet/sing v0.2.4
|
||||||
github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc
|
github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.1
|
github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507
|
||||||
github.com/sagernet/sing-shadowtls v0.1.1
|
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b
|
||||||
github.com/sagernet/sing-tun v0.1.4-0.20230326080954-8848c0e4cbab
|
github.com/sagernet/sing-tun v0.1.4
|
||||||
github.com/sagernet/sing-vmess v0.1.4
|
github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
|
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
|
||||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
|
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
|
||||||
|
|||||||
20
go.sum
20
go.sum
@@ -111,18 +111,18 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byL
|
|||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
||||||
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||||
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
||||||
github.com/sagernet/sing v0.2.3 h1:V50MvZ4c3Iij2lYFWPlzL1PyipwSzjGeN9x+Ox89vpk=
|
github.com/sagernet/sing v0.2.4 h1:gC8BR5sglbJZX23RtMyFa8EETP9YEUADhfbEzU1yVbo=
|
||||||
github.com/sagernet/sing v0.2.3/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
github.com/sagernet/sing v0.2.4/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
||||||
github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc h1:hmbuqKv48SAjiKPoqtJGvS5pEHVPZjTHq9CPwQY2cZ4=
|
github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc h1:hmbuqKv48SAjiKPoqtJGvS5pEHVPZjTHq9CPwQY2cZ4=
|
||||||
github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc/go.mod h1:ZKuuqgsHRxDahYrzgSgy4vIAGGuKPlIf4hLcNzYzLkY=
|
github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc/go.mod h1:ZKuuqgsHRxDahYrzgSgy4vIAGGuKPlIf4hLcNzYzLkY=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.1 h1:FvdLQOqpvxHBJUcUe4fvgiYP2XLLwH5i1DtXQviVEPw=
|
github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507 h1:bAHZCdWqJkb8LEW98+YsMVDXGRMUVjka8IC+St6ot88=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.1/go.mod h1:T/OgurSjsAe+Ug3+6PprXjmgHFmJidjOvQcjXGTKb3I=
|
github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507/go.mod h1:UJjvQGw0lyYaDGIDvUraL16fwaAEH1WFw1Y6sUcMPog=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.1 h1:Gf8YD/4zCjfM9xxVzI0QVYAnjMCtsDHcIp84CUdY3VA=
|
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b h1:ouW/6IDCrxkBe19YSbdCd7buHix7b+UZ6BM4Zz74XF4=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.1/go.mod h1:espnHDPRQeG95ZoU3xfHDFnc6Nt4GvbbuPV2okN+x/E=
|
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b/go.mod h1:oG8bPerYI6cZ74KquY3DvA7ynECyrILPBnce6wtBqeI=
|
||||||
github.com/sagernet/sing-tun v0.1.4-0.20230326080954-8848c0e4cbab h1:a9oeWuPBuIZ70qMhIIH6RrYhp886xN9jJIwsuu4ZFUo=
|
github.com/sagernet/sing-tun v0.1.4 h1:Fa6kgvuM2fPbPu3R97S8L8NgaD5lJq3wQorNuTb5oqo=
|
||||||
github.com/sagernet/sing-tun v0.1.4-0.20230326080954-8848c0e4cbab/go.mod h1:4YxIDEkkCjGXDOTMPw1SXpLmCQUFAWuaQN250oo+928=
|
github.com/sagernet/sing-tun v0.1.4/go.mod h1:7BrtP7NMp9FK5oVsZWg92b7yFrD+sM2+udapFurReyw=
|
||||||
github.com/sagernet/sing-vmess v0.1.4 h1:aZ03KgmR9COPitVqo8NTPHeaHmbJYDZ8mkumnUV4oGQ=
|
github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3 h1:BHOnxrbC929JonuKqFdJ7ZbDp7zs4oTlH5KFvKtWu9U=
|
||||||
github.com/sagernet/sing-vmess v0.1.4/go.mod h1:NensKZJDgfPsUf3TzFD8kTuzI3CQfN1Tj3Eygti5Isg=
|
github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3/go.mod h1:yKrAr+dqZd64DxBXCHWrYicp+n4qbqO73mtwv3dck8U=
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0=
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0=
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 h1:2ItpW1nMNkPzmBTxV0/eClCklHrFSQMnUGcpUmJxVeE=
|
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 h1:2ItpW1nMNkPzmBTxV0/eClCklHrFSQMnUGcpUmJxVeE=
|
||||||
|
|||||||
@@ -609,6 +609,10 @@ func (c *naiveH2Conn) SetWriteDeadline(t time.Time) error {
|
|||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *naiveH2Conn) NeedAdditionalReadDeadline() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (c *naiveH2Conn) UpstreamReader() any {
|
func (c *naiveH2Conn) UpstreamReader() any {
|
||||||
return c.reader
|
return c.reader
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-shadowsocks"
|
|
||||||
"github.com/sagernet/sing-shadowsocks/shadowaead"
|
|
||||||
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
|
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/auth"
|
"github.com/sagernet/sing/common/auth"
|
||||||
@@ -27,7 +25,7 @@ var (
|
|||||||
|
|
||||||
type ShadowsocksMulti struct {
|
type ShadowsocksMulti struct {
|
||||||
myInboundAdapter
|
myInboundAdapter
|
||||||
service shadowsocks.MultiService[int]
|
service *shadowaead_2022.MultiService[int]
|
||||||
users []option.ShadowsocksUser
|
users []option.ShadowsocksUser
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,26 +49,16 @@ func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log.
|
|||||||
} else {
|
} else {
|
||||||
udpTimeout = int64(C.UDPTimeout.Seconds())
|
udpTimeout = int64(C.UDPTimeout.Seconds())
|
||||||
}
|
}
|
||||||
var (
|
if !common.Contains(shadowaead_2022.List, options.Method) {
|
||||||
service shadowsocks.MultiService[int]
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if common.Contains(shadowaead_2022.List, options.Method) {
|
|
||||||
service, err = shadowaead_2022.NewMultiServiceWithPassword[int](
|
|
||||||
options.Method,
|
|
||||||
options.Password,
|
|
||||||
udpTimeout,
|
|
||||||
adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound),
|
|
||||||
router.TimeFunc(),
|
|
||||||
)
|
|
||||||
} else if common.Contains(shadowaead.List, options.Method) {
|
|
||||||
service, err = shadowaead.NewMultiService[int](
|
|
||||||
options.Method,
|
|
||||||
udpTimeout,
|
|
||||||
adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound))
|
|
||||||
} else {
|
|
||||||
return nil, E.New("unsupported method: " + options.Method)
|
return nil, E.New("unsupported method: " + options.Method)
|
||||||
}
|
}
|
||||||
|
service, err := shadowaead_2022.NewMultiServiceWithPassword[int](
|
||||||
|
options.Method,
|
||||||
|
options.Password,
|
||||||
|
udpTimeout,
|
||||||
|
adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound),
|
||||||
|
router.TimeFunc(),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,7 @@ import (
|
|||||||
"github.com/sagernet/sing/common/ranges"
|
"github.com/sagernet/sing/common/ranges"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var _ adapter.Inbound = (*Tun)(nil)
|
||||||
_ adapter.Inbound = (*Tun)(nil)
|
|
||||||
_ tun.Router = (*Tun)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Tun struct {
|
type Tun struct {
|
||||||
tag string
|
tag string
|
||||||
@@ -41,6 +38,10 @@ type Tun struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) {
|
func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) {
|
||||||
|
tunName := options.InterfaceName
|
||||||
|
if tunName == "" {
|
||||||
|
tunName = tun.CalculateInterfaceName("")
|
||||||
|
}
|
||||||
tunMTU := options.MTU
|
tunMTU := options.MTU
|
||||||
if tunMTU == 0 {
|
if tunMTU == 0 {
|
||||||
tunMTU = 9000
|
tunMTU = 9000
|
||||||
@@ -74,7 +75,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
inboundOptions: options.InboundOptions,
|
inboundOptions: options.InboundOptions,
|
||||||
tunOptions: tun.Options{
|
tunOptions: tun.Options{
|
||||||
Name: options.InterfaceName,
|
Name: tunName,
|
||||||
MTU: tunMTU,
|
MTU: tunMTU,
|
||||||
Inet4Address: common.Map(options.Inet4Address, option.ListenPrefix.Build),
|
Inet4Address: common.Map(options.Inet4Address, option.ListenPrefix.Build),
|
||||||
Inet6Address: common.Map(options.Inet6Address, option.ListenPrefix.Build),
|
Inet6Address: common.Map(options.Inet6Address, option.ListenPrefix.Build),
|
||||||
@@ -140,31 +141,21 @@ func (t *Tun) Tag() string {
|
|||||||
|
|
||||||
func (t *Tun) Start() error {
|
func (t *Tun) Start() error {
|
||||||
if C.IsAndroid && t.platformInterface == nil {
|
if C.IsAndroid && t.platformInterface == nil {
|
||||||
t.logger.Trace("building android rules")
|
|
||||||
t.tunOptions.BuildAndroidRules(t.router.PackageManager(), t)
|
t.tunOptions.BuildAndroidRules(t.router.PackageManager(), t)
|
||||||
}
|
}
|
||||||
if t.tunOptions.Name == "" {
|
|
||||||
t.tunOptions.Name = tun.CalculateInterfaceName("")
|
|
||||||
}
|
|
||||||
var (
|
var (
|
||||||
tunInterface tun.Tun
|
tunInterface tun.Tun
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
t.logger.Trace("opening interface")
|
|
||||||
if t.platformInterface != nil {
|
if t.platformInterface != nil {
|
||||||
tunInterface, err = t.platformInterface.OpenTun(t.tunOptions, t.platformOptions)
|
tunInterface, err = t.platformInterface.OpenTun(&t.tunOptions, t.platformOptions)
|
||||||
} else {
|
} else {
|
||||||
tunInterface, err = tun.New(t.tunOptions)
|
tunInterface, err = tun.New(t.tunOptions)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "configure tun interface")
|
return E.Cause(err, "configure tun interface")
|
||||||
}
|
}
|
||||||
t.logger.Trace("creating stack")
|
|
||||||
t.tunIf = tunInterface
|
t.tunIf = tunInterface
|
||||||
var tunRouter tun.Router
|
|
||||||
if len(t.router.IPRules()) > 0 {
|
|
||||||
tunRouter = t
|
|
||||||
}
|
|
||||||
t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
|
t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
|
||||||
Context: t.ctx,
|
Context: t.ctx,
|
||||||
Tun: tunInterface,
|
Tun: tunInterface,
|
||||||
@@ -174,15 +165,13 @@ func (t *Tun) Start() error {
|
|||||||
Inet6Address: t.tunOptions.Inet6Address,
|
Inet6Address: t.tunOptions.Inet6Address,
|
||||||
EndpointIndependentNat: t.endpointIndependentNat,
|
EndpointIndependentNat: t.endpointIndependentNat,
|
||||||
UDPTimeout: t.udpTimeout,
|
UDPTimeout: t.udpTimeout,
|
||||||
Router: tunRouter,
|
|
||||||
Handler: t,
|
Handler: t,
|
||||||
Logger: t.logger,
|
Logger: t.logger,
|
||||||
UnderPlatform: t.platformInterface != nil,
|
ForwarderBindInterface: t.platformInterface != nil,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
t.logger.Trace("starting stack")
|
|
||||||
err = t.tunStack.Start()
|
err = t.tunStack.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -198,21 +187,6 @@ func (t *Tun) Close() error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tun) RouteConnection(session tun.RouteSession, conn tun.RouteContext) tun.RouteAction {
|
|
||||||
ctx := log.ContextWithNewID(t.ctx)
|
|
||||||
var metadata adapter.InboundContext
|
|
||||||
metadata.Inbound = t.tag
|
|
||||||
metadata.InboundType = C.TypeTun
|
|
||||||
metadata.IPVersion = session.IPVersion
|
|
||||||
metadata.Network = tun.NetworkName(session.Network)
|
|
||||||
metadata.Source = M.SocksaddrFromNetIP(session.Source)
|
|
||||||
metadata.Destination = M.SocksaddrFromNetIP(session.Destination)
|
|
||||||
metadata.InboundOptions = t.inboundOptions
|
|
||||||
t.logger.DebugContext(ctx, "incoming connection from ", metadata.Source)
|
|
||||||
t.logger.DebugContext(ctx, "incoming connection to ", metadata.Destination)
|
|
||||||
return t.router.RouteIPConnection(ctx, conn, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error {
|
func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error {
|
||||||
ctx = log.ContextWithNewID(ctx)
|
ctx = log.ContextWithNewID(ctx)
|
||||||
var metadata adapter.InboundContext
|
var metadata adapter.InboundContext
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
"github.com/sagernet/sing-vmess/packetaddr"
|
"github.com/sagernet/sing-vmess/packetaddr"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/auth"
|
"github.com/sagernet/sing/common/auth"
|
||||||
"github.com/sagernet/sing/common/bufio/deadline"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@@ -172,7 +171,7 @@ func (h *VLESS) newPacketConnection(ctx context.Context, conn N.PacketConn, meta
|
|||||||
}
|
}
|
||||||
if metadata.Destination.Fqdn == packetaddr.SeqPacketMagicAddress {
|
if metadata.Destination.Fqdn == packetaddr.SeqPacketMagicAddress {
|
||||||
metadata.Destination = M.Socksaddr{}
|
metadata.Destination = M.Socksaddr{}
|
||||||
conn = deadline.NewPacketConn(packetaddr.NewConn(conn.(vmess.PacketConn), metadata.Destination))
|
conn = packetaddr.NewConn(conn.(vmess.PacketConn), metadata.Destination)
|
||||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet addr connection")
|
h.logger.InfoContext(ctx, "[", user, "] inbound packet addr connection")
|
||||||
} else {
|
} else {
|
||||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
|
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
"github.com/sagernet/sing-vmess/packetaddr"
|
"github.com/sagernet/sing-vmess/packetaddr"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/auth"
|
"github.com/sagernet/sing/common/auth"
|
||||||
"github.com/sagernet/sing/common/bufio/deadline"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@@ -180,7 +179,7 @@ func (h *VMess) newPacketConnection(ctx context.Context, conn N.PacketConn, meta
|
|||||||
}
|
}
|
||||||
if metadata.Destination.Fqdn == packetaddr.SeqPacketMagicAddress {
|
if metadata.Destination.Fqdn == packetaddr.SeqPacketMagicAddress {
|
||||||
metadata.Destination = M.Socksaddr{}
|
metadata.Destination = M.Socksaddr{}
|
||||||
conn = deadline.NewPacketConn(packetaddr.NewConn(conn.(vmess.PacketConn), metadata.Destination))
|
conn = packetaddr.NewConn(conn.(vmess.PacketConn), metadata.Destination)
|
||||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet addr connection")
|
h.logger.InfoContext(ctx, "[", user, "] inbound packet addr connection")
|
||||||
} else {
|
} else {
|
||||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
|
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
|
||||||
|
|||||||
@@ -48,14 +48,12 @@ nav:
|
|||||||
- configuration/dns/index.md
|
- configuration/dns/index.md
|
||||||
- DNS Server: configuration/dns/server.md
|
- DNS Server: configuration/dns/server.md
|
||||||
- DNS Rule: configuration/dns/rule.md
|
- DNS Rule: configuration/dns/rule.md
|
||||||
- FakeIP: configuration/dns/fakeip.md
|
|
||||||
- NTP:
|
- NTP:
|
||||||
- configuration/ntp/index.md
|
- configuration/ntp/index.md
|
||||||
- Route:
|
- Route:
|
||||||
- configuration/route/index.md
|
- configuration/route/index.md
|
||||||
- GeoIP: configuration/route/geoip.md
|
- GeoIP: configuration/route/geoip.md
|
||||||
- Geosite: configuration/route/geosite.md
|
- Geosite: configuration/route/geosite.md
|
||||||
- IP Route Rule: configuration/route/ip-rule.md
|
|
||||||
- Route Rule: configuration/route/rule.md
|
- Route Rule: configuration/route/rule.md
|
||||||
- Protocol Sniff: configuration/route/sniff.md
|
- Protocol Sniff: configuration/route/sniff.md
|
||||||
- Experimental:
|
- Experimental:
|
||||||
@@ -104,7 +102,6 @@ nav:
|
|||||||
- URLTest: configuration/outbound/urltest.md
|
- URLTest: configuration/outbound/urltest.md
|
||||||
- FAQ:
|
- FAQ:
|
||||||
- faq/index.md
|
- faq/index.md
|
||||||
- FakeIP: faq/fakeip.md
|
|
||||||
- Known Issues: faq/known-issues.md
|
- Known Issues: faq/known-issues.md
|
||||||
- Examples:
|
- Examples:
|
||||||
- examples/index.md
|
- examples/index.md
|
||||||
@@ -114,7 +111,6 @@ nav:
|
|||||||
- Shadowsocks: examples/shadowsocks.md
|
- Shadowsocks: examples/shadowsocks.md
|
||||||
- ShadowTLS: examples/shadowtls.md
|
- ShadowTLS: examples/shadowtls.md
|
||||||
- Clash API: examples/clash-api.md
|
- Clash API: examples/clash-api.md
|
||||||
- WireGuard Direct: examples/wireguard-direct.md
|
|
||||||
- Contributing:
|
- Contributing:
|
||||||
- contributing/index.md
|
- contributing/index.md
|
||||||
- Developing:
|
- Developing:
|
||||||
@@ -173,7 +169,6 @@ plugins:
|
|||||||
DNS Rule: DNS 规则
|
DNS Rule: DNS 规则
|
||||||
|
|
||||||
Route: 路由
|
Route: 路由
|
||||||
IP Route Rule: IP 路由规则
|
|
||||||
Route Rule: 路由规则
|
Route Rule: 路由规则
|
||||||
Protocol Sniff: 协议探测
|
Protocol Sniff: 协议探测
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
type ClashAPIOptions struct {
|
type ClashAPIOptions struct {
|
||||||
ExternalController string `json:"external_controller,omitempty"`
|
ExternalController string `json:"external_controller,omitempty"`
|
||||||
ExternalUI string `json:"external_ui,omitempty"`
|
ExternalUI string `json:"external_ui,omitempty"`
|
||||||
ExternalUIDownloadURL string `json:"external_ui_download_url,omitempty"`
|
Secret string `json:"secret,omitempty"`
|
||||||
ExternalUIDownloadDetour string `json:"external_ui_download_detour,omitempty"`
|
DefaultMode string `json:"default_mode,omitempty"`
|
||||||
Secret string `json:"secret,omitempty"`
|
StoreSelected bool `json:"store_selected,omitempty"`
|
||||||
DefaultMode string `json:"default_mode,omitempty"`
|
CacheFile string `json:"cache_file,omitempty"`
|
||||||
StoreSelected bool `json:"store_selected,omitempty"`
|
|
||||||
StoreFakeIP bool `json:"store_fakeip,omitempty"`
|
|
||||||
CacheFile string `json:"cache_file,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SelectorOutboundOptions struct {
|
type SelectorOutboundOptions struct {
|
||||||
|
|||||||
121
option/dns.go
121
option/dns.go
@@ -1,14 +1,27 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/json"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
type DNSOptions struct {
|
type DNSOptions struct {
|
||||||
Servers []DNSServerOptions `json:"servers,omitempty"`
|
Servers []DNSServerOptions `json:"servers,omitempty"`
|
||||||
Rules []DNSRule `json:"rules,omitempty"`
|
Rules []DNSRule `json:"rules,omitempty"`
|
||||||
Final string `json:"final,omitempty"`
|
Final string `json:"final,omitempty"`
|
||||||
ReverseMapping bool `json:"reverse_mapping,omitempty"`
|
|
||||||
FakeIP *DNSFakeIPOptions `json:"fakeip,omitempty"`
|
|
||||||
DNSClientOptions
|
DNSClientOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DNSClientOptions struct {
|
||||||
|
Strategy DomainStrategy `json:"strategy,omitempty"`
|
||||||
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
|
DisableExpire bool `json:"disable_expire,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type DNSServerOptions struct {
|
type DNSServerOptions struct {
|
||||||
Tag string `json:"tag,omitempty"`
|
Tag string `json:"tag,omitempty"`
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
@@ -19,14 +32,96 @@ type DNSServerOptions struct {
|
|||||||
Detour string `json:"detour,omitempty"`
|
Detour string `json:"detour,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSClientOptions struct {
|
type _DNSRule struct {
|
||||||
Strategy DomainStrategy `json:"strategy,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
DefaultOptions DefaultDNSRule `json:"-"`
|
||||||
DisableExpire bool `json:"disable_expire,omitempty"`
|
LogicalOptions LogicalDNSRule `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSFakeIPOptions struct {
|
type DNSRule _DNSRule
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
|
||||||
Inet4Range *ListenPrefix `json:"inet4_range,omitempty"`
|
func (r DNSRule) MarshalJSON() ([]byte, error) {
|
||||||
Inet6Range *ListenPrefix `json:"inet6_range,omitempty"`
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
r.Type = ""
|
||||||
|
v = r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
return MarshallObjects((_DNSRule)(r), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DNSRule) UnmarshalJSON(bytes []byte) error {
|
||||||
|
err := json.Unmarshal(bytes, (*_DNSRule)(r))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case "", C.RuleTypeDefault:
|
||||||
|
r.Type = C.RuleTypeDefault
|
||||||
|
v = &r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = &r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
err = UnmarshallExcluded(bytes, (*_DNSRule)(r), v)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "dns route rule")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultDNSRule struct {
|
||||||
|
Inbound Listable[string] `json:"inbound,omitempty"`
|
||||||
|
IPVersion int `json:"ip_version,omitempty"`
|
||||||
|
QueryType Listable[DNSQueryType] `json:"query_type,omitempty"`
|
||||||
|
Network string `json:"network,omitempty"`
|
||||||
|
AuthUser Listable[string] `json:"auth_user,omitempty"`
|
||||||
|
Protocol Listable[string] `json:"protocol,omitempty"`
|
||||||
|
Domain Listable[string] `json:"domain,omitempty"`
|
||||||
|
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
|
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
|
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
||||||
|
Geosite Listable[string] `json:"geosite,omitempty"`
|
||||||
|
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
||||||
|
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
|
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
||||||
|
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
||||||
|
Port Listable[uint16] `json:"port,omitempty"`
|
||||||
|
PortRange Listable[string] `json:"port_range,omitempty"`
|
||||||
|
ProcessName Listable[string] `json:"process_name,omitempty"`
|
||||||
|
ProcessPath Listable[string] `json:"process_path,omitempty"`
|
||||||
|
PackageName Listable[string] `json:"package_name,omitempty"`
|
||||||
|
User Listable[string] `json:"user,omitempty"`
|
||||||
|
UserID Listable[int32] `json:"user_id,omitempty"`
|
||||||
|
Outbound Listable[string] `json:"outbound,omitempty"`
|
||||||
|
ClashMode string `json:"clash_mode,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Server string `json:"server,omitempty"`
|
||||||
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r DefaultDNSRule) IsValid() bool {
|
||||||
|
var defaultValue DefaultDNSRule
|
||||||
|
defaultValue.Invert = r.Invert
|
||||||
|
defaultValue.Server = r.Server
|
||||||
|
defaultValue.DisableCache = r.DisableCache
|
||||||
|
return !reflect.DeepEqual(r, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogicalDNSRule struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Rules []DefaultDNSRule `json:"rules,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Server string `json:"server,omitempty"`
|
||||||
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r LogicalDNSRule) IsValid() bool {
|
||||||
|
return len(r.Rules) > 0 && common.All(r.Rules, DefaultDNSRule.IsValid)
|
||||||
}
|
}
|
||||||
|
|||||||
101
option/route.go
101
option/route.go
@@ -1,9 +1,17 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/json"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
type RouteOptions struct {
|
type RouteOptions struct {
|
||||||
GeoIP *GeoIPOptions `json:"geoip,omitempty"`
|
GeoIP *GeoIPOptions `json:"geoip,omitempty"`
|
||||||
Geosite *GeositeOptions `json:"geosite,omitempty"`
|
Geosite *GeositeOptions `json:"geosite,omitempty"`
|
||||||
IPRules []IPRule `json:"ip_rules,omitempty"`
|
|
||||||
Rules []Rule `json:"rules,omitempty"`
|
Rules []Rule `json:"rules,omitempty"`
|
||||||
Final string `json:"final,omitempty"`
|
Final string `json:"final,omitempty"`
|
||||||
FindProcess bool `json:"find_process,omitempty"`
|
FindProcess bool `json:"find_process,omitempty"`
|
||||||
@@ -24,3 +32,94 @@ type GeositeOptions struct {
|
|||||||
DownloadURL string `json:"download_url,omitempty"`
|
DownloadURL string `json:"download_url,omitempty"`
|
||||||
DownloadDetour string `json:"download_detour,omitempty"`
|
DownloadDetour string `json:"download_detour,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type _Rule struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
DefaultOptions DefaultRule `json:"-"`
|
||||||
|
LogicalOptions LogicalRule `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rule _Rule
|
||||||
|
|
||||||
|
func (r Rule) MarshalJSON() ([]byte, error) {
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
r.Type = ""
|
||||||
|
v = r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
return MarshallObjects((_Rule)(r), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rule) UnmarshalJSON(bytes []byte) error {
|
||||||
|
err := json.Unmarshal(bytes, (*_Rule)(r))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case "", C.RuleTypeDefault:
|
||||||
|
r.Type = C.RuleTypeDefault
|
||||||
|
v = &r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = &r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
err = UnmarshallExcluded(bytes, (*_Rule)(r), v)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "route rule")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultRule struct {
|
||||||
|
Inbound Listable[string] `json:"inbound,omitempty"`
|
||||||
|
IPVersion int `json:"ip_version,omitempty"`
|
||||||
|
Network string `json:"network,omitempty"`
|
||||||
|
AuthUser Listable[string] `json:"auth_user,omitempty"`
|
||||||
|
Protocol Listable[string] `json:"protocol,omitempty"`
|
||||||
|
Domain Listable[string] `json:"domain,omitempty"`
|
||||||
|
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
|
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
|
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
||||||
|
Geosite Listable[string] `json:"geosite,omitempty"`
|
||||||
|
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
||||||
|
GeoIP Listable[string] `json:"geoip,omitempty"`
|
||||||
|
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
|
IPCIDR Listable[string] `json:"ip_cidr,omitempty"`
|
||||||
|
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
||||||
|
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
||||||
|
Port Listable[uint16] `json:"port,omitempty"`
|
||||||
|
PortRange Listable[string] `json:"port_range,omitempty"`
|
||||||
|
ProcessName Listable[string] `json:"process_name,omitempty"`
|
||||||
|
ProcessPath Listable[string] `json:"process_path,omitempty"`
|
||||||
|
PackageName Listable[string] `json:"package_name,omitempty"`
|
||||||
|
User Listable[string] `json:"user,omitempty"`
|
||||||
|
UserID Listable[int32] `json:"user_id,omitempty"`
|
||||||
|
ClashMode string `json:"clash_mode,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Outbound string `json:"outbound,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r DefaultRule) IsValid() bool {
|
||||||
|
var defaultValue DefaultRule
|
||||||
|
defaultValue.Invert = r.Invert
|
||||||
|
defaultValue.Outbound = r.Outbound
|
||||||
|
return !reflect.DeepEqual(r, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogicalRule struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Rules []DefaultRule `json:"rules,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Outbound string `json:"outbound,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r LogicalRule) IsValid() bool {
|
||||||
|
return len(r.Rules) > 0 && common.All(r.Rules, DefaultRule.IsValid)
|
||||||
|
}
|
||||||
|
|||||||
101
option/rule.go
101
option/rule.go
@@ -1,101 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/json"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type _Rule struct {
|
|
||||||
Type string `json:"type,omitempty"`
|
|
||||||
DefaultOptions DefaultRule `json:"-"`
|
|
||||||
LogicalOptions LogicalRule `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Rule _Rule
|
|
||||||
|
|
||||||
func (r Rule) MarshalJSON() ([]byte, error) {
|
|
||||||
var v any
|
|
||||||
switch r.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
r.Type = ""
|
|
||||||
v = r.DefaultOptions
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
v = r.LogicalOptions
|
|
||||||
default:
|
|
||||||
return nil, E.New("unknown rule type: " + r.Type)
|
|
||||||
}
|
|
||||||
return MarshallObjects((_Rule)(r), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Rule) UnmarshalJSON(bytes []byte) error {
|
|
||||||
err := json.Unmarshal(bytes, (*_Rule)(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var v any
|
|
||||||
switch r.Type {
|
|
||||||
case "", C.RuleTypeDefault:
|
|
||||||
r.Type = C.RuleTypeDefault
|
|
||||||
v = &r.DefaultOptions
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
v = &r.LogicalOptions
|
|
||||||
default:
|
|
||||||
return E.New("unknown rule type: " + r.Type)
|
|
||||||
}
|
|
||||||
err = UnmarshallExcluded(bytes, (*_Rule)(r), v)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "route rule")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultRule struct {
|
|
||||||
Inbound Listable[string] `json:"inbound,omitempty"`
|
|
||||||
IPVersion int `json:"ip_version,omitempty"`
|
|
||||||
Network Listable[string] `json:"network,omitempty"`
|
|
||||||
AuthUser Listable[string] `json:"auth_user,omitempty"`
|
|
||||||
Protocol Listable[string] `json:"protocol,omitempty"`
|
|
||||||
Domain Listable[string] `json:"domain,omitempty"`
|
|
||||||
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
|
||||||
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
|
||||||
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
|
||||||
Geosite Listable[string] `json:"geosite,omitempty"`
|
|
||||||
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
|
||||||
GeoIP Listable[string] `json:"geoip,omitempty"`
|
|
||||||
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
|
||||||
IPCIDR Listable[string] `json:"ip_cidr,omitempty"`
|
|
||||||
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
|
||||||
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
|
||||||
Port Listable[uint16] `json:"port,omitempty"`
|
|
||||||
PortRange Listable[string] `json:"port_range,omitempty"`
|
|
||||||
ProcessName Listable[string] `json:"process_name,omitempty"`
|
|
||||||
ProcessPath Listable[string] `json:"process_path,omitempty"`
|
|
||||||
PackageName Listable[string] `json:"package_name,omitempty"`
|
|
||||||
User Listable[string] `json:"user,omitempty"`
|
|
||||||
UserID Listable[int32] `json:"user_id,omitempty"`
|
|
||||||
ClashMode string `json:"clash_mode,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Outbound string `json:"outbound,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r DefaultRule) IsValid() bool {
|
|
||||||
var defaultValue DefaultRule
|
|
||||||
defaultValue.Invert = r.Invert
|
|
||||||
defaultValue.Outbound = r.Outbound
|
|
||||||
return !reflect.DeepEqual(r, defaultValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogicalRule struct {
|
|
||||||
Mode string `json:"mode"`
|
|
||||||
Rules []DefaultRule `json:"rules,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Outbound string `json:"outbound,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r LogicalRule) IsValid() bool {
|
|
||||||
return len(r.Rules) > 0 && common.All(r.Rules, DefaultRule.IsValid)
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/json"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type _DNSRule struct {
|
|
||||||
Type string `json:"type,omitempty"`
|
|
||||||
DefaultOptions DefaultDNSRule `json:"-"`
|
|
||||||
LogicalOptions LogicalDNSRule `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DNSRule _DNSRule
|
|
||||||
|
|
||||||
func (r DNSRule) MarshalJSON() ([]byte, error) {
|
|
||||||
var v any
|
|
||||||
switch r.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
r.Type = ""
|
|
||||||
v = r.DefaultOptions
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
v = r.LogicalOptions
|
|
||||||
default:
|
|
||||||
return nil, E.New("unknown rule type: " + r.Type)
|
|
||||||
}
|
|
||||||
return MarshallObjects((_DNSRule)(r), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DNSRule) UnmarshalJSON(bytes []byte) error {
|
|
||||||
err := json.Unmarshal(bytes, (*_DNSRule)(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var v any
|
|
||||||
switch r.Type {
|
|
||||||
case "", C.RuleTypeDefault:
|
|
||||||
r.Type = C.RuleTypeDefault
|
|
||||||
v = &r.DefaultOptions
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
v = &r.LogicalOptions
|
|
||||||
default:
|
|
||||||
return E.New("unknown rule type: " + r.Type)
|
|
||||||
}
|
|
||||||
err = UnmarshallExcluded(bytes, (*_DNSRule)(r), v)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "dns route rule")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultDNSRule struct {
|
|
||||||
Inbound Listable[string] `json:"inbound,omitempty"`
|
|
||||||
IPVersion int `json:"ip_version,omitempty"`
|
|
||||||
QueryType Listable[DNSQueryType] `json:"query_type,omitempty"`
|
|
||||||
Network Listable[string] `json:"network,omitempty"`
|
|
||||||
AuthUser Listable[string] `json:"auth_user,omitempty"`
|
|
||||||
Protocol Listable[string] `json:"protocol,omitempty"`
|
|
||||||
Domain Listable[string] `json:"domain,omitempty"`
|
|
||||||
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
|
||||||
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
|
||||||
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
|
||||||
Geosite Listable[string] `json:"geosite,omitempty"`
|
|
||||||
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
|
||||||
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
|
||||||
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
|
||||||
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
|
||||||
Port Listable[uint16] `json:"port,omitempty"`
|
|
||||||
PortRange Listable[string] `json:"port_range,omitempty"`
|
|
||||||
ProcessName Listable[string] `json:"process_name,omitempty"`
|
|
||||||
ProcessPath Listable[string] `json:"process_path,omitempty"`
|
|
||||||
PackageName Listable[string] `json:"package_name,omitempty"`
|
|
||||||
User Listable[string] `json:"user,omitempty"`
|
|
||||||
UserID Listable[int32] `json:"user_id,omitempty"`
|
|
||||||
Outbound Listable[string] `json:"outbound,omitempty"`
|
|
||||||
ClashMode string `json:"clash_mode,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Server string `json:"server,omitempty"`
|
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
|
||||||
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r DefaultDNSRule) IsValid() bool {
|
|
||||||
var defaultValue DefaultDNSRule
|
|
||||||
defaultValue.Invert = r.Invert
|
|
||||||
defaultValue.Server = r.Server
|
|
||||||
defaultValue.DisableCache = r.DisableCache
|
|
||||||
defaultValue.RewriteTTL = r.RewriteTTL
|
|
||||||
return !reflect.DeepEqual(r, defaultValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogicalDNSRule struct {
|
|
||||||
Mode string `json:"mode"`
|
|
||||||
Rules []DefaultDNSRule `json:"rules,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Server string `json:"server,omitempty"`
|
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
|
||||||
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r LogicalDNSRule) IsValid() bool {
|
|
||||||
return len(r.Rules) > 0 && common.All(r.Rules, DefaultDNSRule.IsValid)
|
|
||||||
}
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/json"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type _IPRule struct {
|
|
||||||
Type string `json:"type,omitempty"`
|
|
||||||
DefaultOptions DefaultIPRule `json:"-"`
|
|
||||||
LogicalOptions LogicalIPRule `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type IPRule _IPRule
|
|
||||||
|
|
||||||
func (r IPRule) MarshalJSON() ([]byte, error) {
|
|
||||||
var v any
|
|
||||||
switch r.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
r.Type = ""
|
|
||||||
v = r.DefaultOptions
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
v = r.LogicalOptions
|
|
||||||
default:
|
|
||||||
return nil, E.New("unknown rule type: " + r.Type)
|
|
||||||
}
|
|
||||||
return MarshallObjects((_IPRule)(r), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *IPRule) UnmarshalJSON(bytes []byte) error {
|
|
||||||
err := json.Unmarshal(bytes, (*_IPRule)(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var v any
|
|
||||||
switch r.Type {
|
|
||||||
case "", C.RuleTypeDefault:
|
|
||||||
r.Type = C.RuleTypeDefault
|
|
||||||
v = &r.DefaultOptions
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
v = &r.LogicalOptions
|
|
||||||
default:
|
|
||||||
return E.New("unknown rule type: " + r.Type)
|
|
||||||
}
|
|
||||||
err = UnmarshallExcluded(bytes, (*_IPRule)(r), v)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "ip route rule")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultIPRule struct {
|
|
||||||
Inbound Listable[string] `json:"inbound,omitempty"`
|
|
||||||
IPVersion int `json:"ip_version,omitempty"`
|
|
||||||
Network Listable[string] `json:"network,omitempty"`
|
|
||||||
Domain Listable[string] `json:"domain,omitempty"`
|
|
||||||
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
|
||||||
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
|
||||||
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
|
||||||
Geosite Listable[string] `json:"geosite,omitempty"`
|
|
||||||
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
|
||||||
GeoIP Listable[string] `json:"geoip,omitempty"`
|
|
||||||
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
|
||||||
IPCIDR Listable[string] `json:"ip_cidr,omitempty"`
|
|
||||||
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
|
||||||
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
|
||||||
Port Listable[uint16] `json:"port,omitempty"`
|
|
||||||
PortRange Listable[string] `json:"port_range,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Action RouteAction `json:"action,omitempty"`
|
|
||||||
Outbound string `json:"outbound,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RouteAction tun.ActionType
|
|
||||||
|
|
||||||
func (a RouteAction) MarshalJSON() ([]byte, error) {
|
|
||||||
typeName, err := tun.ActionTypeName(tun.ActionType(a))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return json.Marshal(typeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *RouteAction) UnmarshalJSON(bytes []byte) error {
|
|
||||||
var value string
|
|
||||||
err := json.Unmarshal(bytes, &value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
actionType, err := tun.ParseActionType(value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*a = RouteAction(actionType)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r DefaultIPRule) IsValid() bool {
|
|
||||||
var defaultValue DefaultIPRule
|
|
||||||
defaultValue.Invert = r.Invert
|
|
||||||
defaultValue.Outbound = r.Outbound
|
|
||||||
return !reflect.DeepEqual(r, defaultValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogicalIPRule struct {
|
|
||||||
Mode string `json:"mode"`
|
|
||||||
Rules []DefaultIPRule `json:"rules,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Action RouteAction `json:"action,omitempty"`
|
|
||||||
Outbound string `json:"outbound,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r LogicalIPRule) IsValid() bool {
|
|
||||||
return len(r.Rules) > 0 && common.All(r.Rules, DefaultIPRule.IsValid)
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@ type ShadowsocksInboundOptions struct {
|
|||||||
ListenOptions
|
ListenOptions
|
||||||
Network NetworkList `json:"network,omitempty"`
|
Network NetworkList `json:"network,omitempty"`
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password"`
|
||||||
Users []ShadowsocksUser `json:"users,omitempty"`
|
Users []ShadowsocksUser `json:"users,omitempty"`
|
||||||
Destinations []ShadowsocksDestination `json:"destinations,omitempty"`
|
Destinations []ShadowsocksDestination `json:"destinations,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,9 +27,7 @@ type SocksOutboundOptions struct {
|
|||||||
type HTTPOutboundOptions struct {
|
type HTTPOutboundOptions struct {
|
||||||
DialerOptions
|
DialerOptions
|
||||||
ServerOptions
|
ServerOptions
|
||||||
Username string `json:"username,omitempty"`
|
Username string `json:"username,omitempty"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||||
Path string `json:"path,omitempty"`
|
|
||||||
Headers map[string]Listable[string] `json:"headers,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,25 +2,15 @@ package option
|
|||||||
|
|
||||||
type WireGuardOutboundOptions struct {
|
type WireGuardOutboundOptions struct {
|
||||||
DialerOptions
|
DialerOptions
|
||||||
|
ServerOptions
|
||||||
SystemInterface bool `json:"system_interface,omitempty"`
|
SystemInterface bool `json:"system_interface,omitempty"`
|
||||||
InterfaceName string `json:"interface_name,omitempty"`
|
InterfaceName string `json:"interface_name,omitempty"`
|
||||||
LocalAddress Listable[ListenPrefix] `json:"local_address"`
|
LocalAddress Listable[ListenPrefix] `json:"local_address"`
|
||||||
PrivateKey string `json:"private_key"`
|
PrivateKey string `json:"private_key"`
|
||||||
Peers []WireGuardPeer `json:"peers,omitempty"`
|
PeerPublicKey string `json:"peer_public_key"`
|
||||||
ServerOptions
|
PreSharedKey string `json:"pre_shared_key,omitempty"`
|
||||||
PeerPublicKey string `json:"peer_public_key"`
|
Reserved []uint8 `json:"reserved,omitempty"`
|
||||||
PreSharedKey string `json:"pre_shared_key,omitempty"`
|
Workers int `json:"workers,omitempty"`
|
||||||
Reserved []uint8 `json:"reserved,omitempty"`
|
MTU uint32 `json:"mtu,omitempty"`
|
||||||
Workers int `json:"workers,omitempty"`
|
Network NetworkList `json:"network,omitempty"`
|
||||||
MTU uint32 `json:"mtu,omitempty"`
|
|
||||||
Network NetworkList `json:"network,omitempty"`
|
|
||||||
IPRewrite bool `json:"ip_rewrite,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WireGuardPeer struct {
|
|
||||||
ServerOptions
|
|
||||||
PublicKey string `json:"public_key,omitempty"`
|
|
||||||
PreSharedKey string `json:"pre_shared_key,omitempty"`
|
|
||||||
AllowedIPs Listable[string] `json:"allowed_ips,omitempty"`
|
|
||||||
Reserved []uint8 `json:"reserved,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, t
|
|||||||
case C.TypeSelector:
|
case C.TypeSelector:
|
||||||
return NewSelector(router, logger, tag, options.SelectorOptions)
|
return NewSelector(router, logger, tag, options.SelectorOptions)
|
||||||
case C.TypeURLTest:
|
case C.TypeURLTest:
|
||||||
return NewURLTest(ctx, router, logger, tag, options.URLTestOptions)
|
return NewURLTest(router, logger, tag, options.URLTestOptions)
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown outbound type: ", options.Type)
|
return nil, E.New("unknown outbound type: ", options.Type)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,10 +39,6 @@ func (a *myOutboundAdapter) Network() []string {
|
|||||||
return a.network
|
return a.network
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *myOutboundAdapter) NewError(ctx context.Context, err error) {
|
|
||||||
NewError(a.logger, ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
|
func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
ctx = adapter.WithContext(ctx, &metadata)
|
ctx = adapter.WithContext(ctx, &metadata)
|
||||||
var outConn net.Conn
|
var outConn net.Conn
|
||||||
@@ -125,12 +121,3 @@ func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) erro
|
|||||||
}
|
}
|
||||||
return bufio.CopyConn(ctx, conn, serverConn)
|
return bufio.CopyConn(ctx, conn, serverConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewError(logger log.ContextLogger, ctx context.Context, err error) {
|
|
||||||
common.Close(err)
|
|
||||||
if E.IsClosedOrCanceled(err) {
|
|
||||||
logger.DebugContext(ctx, "connection closed: ", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.ErrorContext(ctx, err)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package outbound
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -30,13 +29,6 @@ func NewHTTP(router adapter.Router, logger log.ContextLogger, tag string, option
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var headers http.Header
|
|
||||||
if options.Headers != nil {
|
|
||||||
headers = make(http.Header)
|
|
||||||
for key, values := range options.Headers {
|
|
||||||
headers[key] = values
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &HTTP{
|
return &HTTP{
|
||||||
myOutboundAdapter{
|
myOutboundAdapter{
|
||||||
protocol: C.TypeHTTP,
|
protocol: C.TypeHTTP,
|
||||||
@@ -50,8 +42,6 @@ func NewHTTP(router adapter.Router, logger log.ContextLogger, tag string, option
|
|||||||
Server: options.ServerOptions.Build(),
|
Server: options.ServerOptions.Build(),
|
||||||
Username: options.Username,
|
Username: options.Username,
|
||||||
Password: options.Password,
|
Password: options.Password,
|
||||||
Path: options.Path,
|
|
||||||
Headers: headers,
|
|
||||||
}),
|
}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -20,14 +19,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ adapter.Outbound = (*URLTest)(nil)
|
_ adapter.Outbound = (*URLTest)(nil)
|
||||||
_ adapter.OutboundGroup = (*URLTest)(nil)
|
_ adapter.OutboundGroup = (*URLTest)(nil)
|
||||||
_ adapter.InterfaceUpdateListener = (*URLTest)(nil)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type URLTest struct {
|
type URLTest struct {
|
||||||
myOutboundAdapter
|
myOutboundAdapter
|
||||||
ctx context.Context
|
|
||||||
tags []string
|
tags []string
|
||||||
link string
|
link string
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
@@ -35,7 +32,7 @@ type URLTest struct {
|
|||||||
group *URLTestGroup
|
group *URLTestGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (*URLTest, error) {
|
func NewURLTest(router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (*URLTest, error) {
|
||||||
outbound := &URLTest{
|
outbound := &URLTest{
|
||||||
myOutboundAdapter: myOutboundAdapter{
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
protocol: C.TypeURLTest,
|
protocol: C.TypeURLTest,
|
||||||
@@ -43,7 +40,6 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
},
|
},
|
||||||
ctx: ctx,
|
|
||||||
tags: options.Outbounds,
|
tags: options.Outbounds,
|
||||||
link: options.URL,
|
link: options.URL,
|
||||||
interval: time.Duration(options.Interval),
|
interval: time.Duration(options.Interval),
|
||||||
@@ -71,12 +67,11 @@ func (s *URLTest) Start() error {
|
|||||||
}
|
}
|
||||||
outbounds = append(outbounds, detour)
|
outbounds = append(outbounds, detour)
|
||||||
}
|
}
|
||||||
s.group = NewURLTestGroup(s.ctx, s.router, s.logger, outbounds, s.link, s.interval, s.tolerance)
|
s.group = NewURLTestGroup(s.router, s.logger, outbounds, s.link, s.interval, s.tolerance)
|
||||||
go s.group.CheckOutbounds()
|
return s.group.Start()
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *URLTest) Close() error {
|
func (s URLTest) Close() error {
|
||||||
return common.Close(
|
return common.Close(
|
||||||
common.PtrOrNil(s.group),
|
common.PtrOrNil(s.group),
|
||||||
)
|
)
|
||||||
@@ -90,31 +85,39 @@ func (s *URLTest) All() []string {
|
|||||||
return s.tags
|
return s.tags
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *URLTest) URLTest(ctx context.Context, link string) (map[string]uint16, error) {
|
|
||||||
return s.group.URLTest(ctx, link)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *URLTest) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (s *URLTest) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
s.group.Start()
|
|
||||||
outbound := s.group.Select(network)
|
outbound := s.group.Select(network)
|
||||||
conn, err := outbound.DialContext(ctx, network, destination)
|
conn, err := outbound.DialContext(ctx, network, destination)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
s.logger.ErrorContext(ctx, err)
|
s.logger.ErrorContext(ctx, err)
|
||||||
s.group.history.DeleteURLTestHistory(outbound.Tag())
|
go s.group.checkOutbounds()
|
||||||
|
outbounds := s.group.Fallback(outbound)
|
||||||
|
for _, fallback := range outbounds {
|
||||||
|
conn, err = fallback.DialContext(ctx, network, destination)
|
||||||
|
if err == nil {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
s.group.Start()
|
|
||||||
outbound := s.group.Select(N.NetworkUDP)
|
outbound := s.group.Select(N.NetworkUDP)
|
||||||
conn, err := outbound.ListenPacket(ctx, destination)
|
conn, err := outbound.ListenPacket(ctx, destination)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
s.logger.ErrorContext(ctx, err)
|
s.logger.ErrorContext(ctx, err)
|
||||||
s.group.history.DeleteURLTestHistory(outbound.Tag())
|
go s.group.checkOutbounds()
|
||||||
|
outbounds := s.group.Fallback(outbound)
|
||||||
|
for _, fallback := range outbounds {
|
||||||
|
conn, err = fallback.ListenPacket(ctx, destination)
|
||||||
|
if err == nil {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,13 +129,7 @@ func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, me
|
|||||||
return NewPacketConnection(ctx, s, conn, metadata)
|
return NewPacketConnection(ctx, s, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *URLTest) InterfaceUpdated() error {
|
|
||||||
go s.group.CheckOutbounds()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type URLTestGroup struct {
|
type URLTestGroup struct {
|
||||||
ctx context.Context
|
|
||||||
router adapter.Router
|
router adapter.Router
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
outbounds []adapter.Outbound
|
outbounds []adapter.Outbound
|
||||||
@@ -141,12 +138,15 @@ type URLTestGroup struct {
|
|||||||
tolerance uint16
|
tolerance uint16
|
||||||
history *urltest.HistoryStorage
|
history *urltest.HistoryStorage
|
||||||
|
|
||||||
access sync.Mutex
|
|
||||||
ticker *time.Ticker
|
ticker *time.Ticker
|
||||||
close chan struct{}
|
close chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewURLTestGroup(ctx context.Context, router adapter.Router, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16) *URLTestGroup {
|
func NewURLTestGroup(router adapter.Router, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16) *URLTestGroup {
|
||||||
|
if link == "" {
|
||||||
|
//goland:noinspection HttpUrlsUsage
|
||||||
|
link = "http://www.gstatic.com/generate_204"
|
||||||
|
}
|
||||||
if interval == 0 {
|
if interval == 0 {
|
||||||
interval = C.DefaultURLTestInterval
|
interval = C.DefaultURLTestInterval
|
||||||
}
|
}
|
||||||
@@ -160,7 +160,6 @@ func NewURLTestGroup(ctx context.Context, router adapter.Router, logger log.Logg
|
|||||||
history = urltest.NewHistoryStorage()
|
history = urltest.NewHistoryStorage()
|
||||||
}
|
}
|
||||||
return &URLTestGroup{
|
return &URLTestGroup{
|
||||||
ctx: ctx,
|
|
||||||
router: router,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
outbounds: outbounds,
|
outbounds: outbounds,
|
||||||
@@ -172,23 +171,13 @@ func NewURLTestGroup(ctx context.Context, router adapter.Router, logger log.Logg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *URLTestGroup) Start() {
|
func (g *URLTestGroup) Start() error {
|
||||||
if g.ticker != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
g.access.Lock()
|
|
||||||
defer g.access.Unlock()
|
|
||||||
if g.ticker != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
g.ticker = time.NewTicker(g.interval)
|
g.ticker = time.NewTicker(g.interval)
|
||||||
go g.loopCheck()
|
go g.loopCheck()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *URLTestGroup) Close() error {
|
func (g *URLTestGroup) Close() error {
|
||||||
if g.ticker == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
g.ticker.Stop()
|
g.ticker.Stop()
|
||||||
close(g.close)
|
close(g.close)
|
||||||
return nil
|
return nil
|
||||||
@@ -248,26 +237,20 @@ func (g *URLTestGroup) Fallback(used adapter.Outbound) []adapter.Outbound {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *URLTestGroup) loopCheck() {
|
func (g *URLTestGroup) loopCheck() {
|
||||||
go g.CheckOutbounds()
|
go g.checkOutbounds()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-g.close:
|
case <-g.close:
|
||||||
return
|
return
|
||||||
case <-g.ticker.C:
|
case <-g.ticker.C:
|
||||||
g.CheckOutbounds()
|
g.checkOutbounds()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *URLTestGroup) CheckOutbounds() {
|
func (g *URLTestGroup) checkOutbounds() {
|
||||||
_, _ = g.URLTest(g.ctx, g.link)
|
b, _ := batch.New(context.Background(), batch.WithConcurrencyNum[any](10))
|
||||||
}
|
|
||||||
|
|
||||||
func (g *URLTestGroup) URLTest(ctx context.Context, link string) (map[string]uint16, error) {
|
|
||||||
b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10))
|
|
||||||
checked := make(map[string]bool)
|
checked := make(map[string]bool)
|
||||||
result := make(map[string]uint16)
|
|
||||||
var resultAccess sync.Mutex
|
|
||||||
for _, detour := range g.outbounds {
|
for _, detour := range g.outbounds {
|
||||||
tag := detour.Tag()
|
tag := detour.Tag()
|
||||||
realTag := RealTag(detour)
|
realTag := RealTag(detour)
|
||||||
@@ -286,7 +269,7 @@ func (g *URLTestGroup) URLTest(ctx context.Context, link string) (map[string]uin
|
|||||||
b.Go(realTag, func() (any, error) {
|
b.Go(realTag, func() (any, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), C.TCPTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), C.TCPTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
t, err := urltest.URLTest(ctx, link, p)
|
t, err := urltest.URLTest(ctx, g.link, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.logger.Debug("outbound ", tag, " unavailable: ", err)
|
g.logger.Debug("outbound ", tag, " unavailable: ", err)
|
||||||
g.history.DeleteURLTestHistory(realTag)
|
g.history.DeleteURLTestHistory(realTag)
|
||||||
@@ -296,13 +279,9 @@ func (g *URLTestGroup) URLTest(ctx context.Context, link string) (map[string]uin
|
|||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
Delay: t,
|
Delay: t,
|
||||||
})
|
})
|
||||||
resultAccess.Lock()
|
|
||||||
result[tag] = t
|
|
||||||
resultAccess.Unlock()
|
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
b.Wait()
|
b.Wait()
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
@@ -28,7 +26,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ adapter.IPOutbound = (*WireGuard)(nil)
|
_ adapter.Outbound = (*WireGuard)(nil)
|
||||||
_ adapter.InterfaceUpdateListener = (*WireGuard)(nil)
|
_ adapter.InterfaceUpdateListener = (*WireGuard)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,7 +34,6 @@ type WireGuard struct {
|
|||||||
myOutboundAdapter
|
myOutboundAdapter
|
||||||
bind *wireguard.ClientBind
|
bind *wireguard.ClientBind
|
||||||
device *device.Device
|
device *device.Device
|
||||||
natDevice wireguard.NatDevice
|
|
||||||
tunDevice wireguard.Device
|
tunDevice wireguard.Device
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,22 +54,13 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
|
|||||||
}
|
}
|
||||||
copy(reserved[:], options.Reserved)
|
copy(reserved[:], options.Reserved)
|
||||||
}
|
}
|
||||||
var isConnect bool
|
peerAddr := options.ServerOptions.Build()
|
||||||
var connectAddr M.Socksaddr
|
outbound.bind = wireguard.NewClientBind(ctx, dialer.New(router, options.DialerOptions), peerAddr, reserved)
|
||||||
if len(options.Peers) < 2 {
|
|
||||||
isConnect = true
|
|
||||||
if len(options.Peers) == 1 {
|
|
||||||
connectAddr = options.Peers[0].ServerOptions.Build()
|
|
||||||
} else {
|
|
||||||
connectAddr = options.ServerOptions.Build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
outbound.bind = wireguard.NewClientBind(ctx, outbound, dialer.New(router, options.DialerOptions), isConnect, connectAddr, reserved)
|
|
||||||
localPrefixes := common.Map(options.LocalAddress, option.ListenPrefix.Build)
|
localPrefixes := common.Map(options.LocalAddress, option.ListenPrefix.Build)
|
||||||
if len(localPrefixes) == 0 {
|
if len(localPrefixes) == 0 {
|
||||||
return nil, E.New("missing local address")
|
return nil, E.New("missing local address")
|
||||||
}
|
}
|
||||||
var privateKey string
|
var privateKey, peerPublicKey, preSharedKey string
|
||||||
{
|
{
|
||||||
bytes, err := base64.StdEncoding.DecodeString(options.PrivateKey)
|
bytes, err := base64.StdEncoding.DecodeString(options.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -80,103 +68,55 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
|
|||||||
}
|
}
|
||||||
privateKey = hex.EncodeToString(bytes)
|
privateKey = hex.EncodeToString(bytes)
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
bytes, err := base64.StdEncoding.DecodeString(options.PeerPublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "decode peer public key")
|
||||||
|
}
|
||||||
|
peerPublicKey = hex.EncodeToString(bytes)
|
||||||
|
}
|
||||||
|
if options.PreSharedKey != "" {
|
||||||
|
bytes, err := base64.StdEncoding.DecodeString(options.PreSharedKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "decode pre shared key")
|
||||||
|
}
|
||||||
|
preSharedKey = hex.EncodeToString(bytes)
|
||||||
|
}
|
||||||
ipcConf := "private_key=" + privateKey
|
ipcConf := "private_key=" + privateKey
|
||||||
if len(options.Peers) > 0 {
|
ipcConf += "\npublic_key=" + peerPublicKey
|
||||||
for i, peer := range options.Peers {
|
ipcConf += "\nendpoint=" + peerAddr.String()
|
||||||
var peerPublicKey, preSharedKey string
|
if preSharedKey != "" {
|
||||||
{
|
ipcConf += "\npreshared_key=" + preSharedKey
|
||||||
bytes, err := base64.StdEncoding.DecodeString(peer.PublicKey)
|
}
|
||||||
if err != nil {
|
var has4, has6 bool
|
||||||
return nil, E.Cause(err, "decode public key for peer ", i)
|
for _, address := range localPrefixes {
|
||||||
}
|
if address.Addr().Is4() {
|
||||||
peerPublicKey = hex.EncodeToString(bytes)
|
has4 = true
|
||||||
}
|
} else {
|
||||||
if peer.PreSharedKey != "" {
|
has6 = true
|
||||||
bytes, err := base64.StdEncoding.DecodeString(peer.PreSharedKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "decode pre shared key for peer ", i)
|
|
||||||
}
|
|
||||||
preSharedKey = hex.EncodeToString(bytes)
|
|
||||||
}
|
|
||||||
destination := peer.ServerOptions.Build()
|
|
||||||
ipcConf += "\npublic_key=" + peerPublicKey
|
|
||||||
ipcConf += "\nendpoint=" + destination.String()
|
|
||||||
if preSharedKey != "" {
|
|
||||||
ipcConf += "\npreshared_key=" + preSharedKey
|
|
||||||
}
|
|
||||||
if len(peer.AllowedIPs) == 0 {
|
|
||||||
return nil, E.New("missing allowed_ips for peer ", i)
|
|
||||||
}
|
|
||||||
for _, allowedIP := range peer.AllowedIPs {
|
|
||||||
ipcConf += "\nallowed_ip=" + allowedIP
|
|
||||||
}
|
|
||||||
if len(peer.Reserved) > 0 {
|
|
||||||
if len(peer.Reserved) != 3 {
|
|
||||||
return nil, E.New("invalid reserved value for peer ", i, ", required 3 bytes, got ", len(peer.Reserved))
|
|
||||||
}
|
|
||||||
copy(reserved[:], options.Reserved)
|
|
||||||
outbound.bind.SetReservedForEndpoint(destination, reserved)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var peerPublicKey, preSharedKey string
|
|
||||||
{
|
|
||||||
bytes, err := base64.StdEncoding.DecodeString(options.PeerPublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "decode peer public key")
|
|
||||||
}
|
|
||||||
peerPublicKey = hex.EncodeToString(bytes)
|
|
||||||
}
|
|
||||||
if options.PreSharedKey != "" {
|
|
||||||
bytes, err := base64.StdEncoding.DecodeString(options.PreSharedKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "decode pre shared key")
|
|
||||||
}
|
|
||||||
preSharedKey = hex.EncodeToString(bytes)
|
|
||||||
}
|
|
||||||
ipcConf += "\npublic_key=" + peerPublicKey
|
|
||||||
ipcConf += "\nendpoint=" + options.ServerOptions.Build().String()
|
|
||||||
if preSharedKey != "" {
|
|
||||||
ipcConf += "\npreshared_key=" + preSharedKey
|
|
||||||
}
|
|
||||||
var has4, has6 bool
|
|
||||||
for _, address := range localPrefixes {
|
|
||||||
if address.Addr().Is4() {
|
|
||||||
has4 = true
|
|
||||||
} else {
|
|
||||||
has6 = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if has4 {
|
|
||||||
ipcConf += "\nallowed_ip=0.0.0.0/0"
|
|
||||||
}
|
|
||||||
if has6 {
|
|
||||||
ipcConf += "\nallowed_ip=::/0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if has4 {
|
||||||
|
ipcConf += "\nallowed_ip=0.0.0.0/0"
|
||||||
|
}
|
||||||
|
if has6 {
|
||||||
|
ipcConf += "\nallowed_ip=::/0"
|
||||||
|
}
|
||||||
mtu := options.MTU
|
mtu := options.MTU
|
||||||
if mtu == 0 {
|
if mtu == 0 {
|
||||||
mtu = 1408
|
mtu = 1408
|
||||||
}
|
}
|
||||||
var tunDevice wireguard.Device
|
var wireTunDevice wireguard.Device
|
||||||
var err error
|
var err error
|
||||||
if !options.SystemInterface && tun.WithGVisor {
|
if !options.SystemInterface && tun.WithGVisor {
|
||||||
tunDevice, err = wireguard.NewStackDevice(localPrefixes, mtu, options.IPRewrite)
|
wireTunDevice, err = wireguard.NewStackDevice(localPrefixes, mtu)
|
||||||
} else {
|
} else {
|
||||||
tunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, localPrefixes, mtu)
|
wireTunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, localPrefixes, mtu)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create WireGuard device")
|
return nil, E.Cause(err, "create WireGuard device")
|
||||||
}
|
}
|
||||||
natDevice, isNatDevice := tunDevice.(wireguard.NatDevice)
|
wgDevice := device.NewDevice(wireTunDevice, outbound.bind, &device.Logger{
|
||||||
if !isNatDevice && router.NatRequired(tag) {
|
|
||||||
natDevice = wireguard.NewNATDevice(tunDevice, options.IPRewrite)
|
|
||||||
}
|
|
||||||
deviceInput := tunDevice
|
|
||||||
if natDevice != nil {
|
|
||||||
deviceInput = natDevice
|
|
||||||
}
|
|
||||||
wgDevice := device.NewDevice(deviceInput, outbound.bind, &device.Logger{
|
|
||||||
Verbosef: func(format string, args ...interface{}) {
|
Verbosef: func(format string, args ...interface{}) {
|
||||||
logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
|
logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
|
||||||
},
|
},
|
||||||
@@ -192,8 +132,7 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
|
|||||||
return nil, E.Cause(err, "setup wireguard")
|
return nil, E.Cause(err, "setup wireguard")
|
||||||
}
|
}
|
||||||
outbound.device = wgDevice
|
outbound.device = wgDevice
|
||||||
outbound.natDevice = natDevice
|
outbound.tunDevice = wireTunDevice
|
||||||
outbound.tunDevice = tunDevice
|
|
||||||
return outbound, nil
|
return outbound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,27 +171,6 @@ func (w *WireGuard) NewPacketConnection(ctx context.Context, conn N.PacketConn,
|
|||||||
return NewPacketConnection(ctx, w, conn, metadata)
|
return NewPacketConnection(ctx, w, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WireGuard) NewIPConnection(ctx context.Context, conn tun.RouteContext, metadata adapter.InboundContext) (tun.DirectDestination, error) {
|
|
||||||
if w.natDevice == nil {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
session := tun.RouteSession{
|
|
||||||
IPVersion: metadata.IPVersion,
|
|
||||||
Network: tun.NetworkFromName(metadata.Network),
|
|
||||||
Source: metadata.Source.AddrPort(),
|
|
||||||
Destination: metadata.Destination.AddrPort(),
|
|
||||||
}
|
|
||||||
switch session.Network {
|
|
||||||
case syscall.IPPROTO_TCP:
|
|
||||||
w.logger.InfoContext(ctx, "linked connection to ", metadata.Destination)
|
|
||||||
case syscall.IPPROTO_UDP:
|
|
||||||
w.logger.InfoContext(ctx, "linked packet connection to ", metadata.Destination)
|
|
||||||
default:
|
|
||||||
w.logger.InfoContext(ctx, "linked ", metadata.Network, " connection to ", metadata.Destination.AddrString())
|
|
||||||
}
|
|
||||||
return w.natDevice.CreateDestination(session, conn), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WireGuard) Start() error {
|
func (w *WireGuard) Start() error {
|
||||||
return w.tunDevice.Start()
|
return w.tunDevice.Start()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
var _ control.InterfaceFinder = (*myInterfaceFinder)(nil)
|
var _ control.InterfaceFinder = (*myInterfaceFinder)(nil)
|
||||||
|
|
||||||
type myInterfaceFinder struct {
|
type myInterfaceFinder struct {
|
||||||
ifs []net.Interface
|
interfaces []net.Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *myInterfaceFinder) update() error {
|
func (f *myInterfaceFinder) update() error {
|
||||||
@@ -17,12 +17,16 @@ func (f *myInterfaceFinder) update() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
f.ifs = ifs
|
f.interfaces = ifs
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *myInterfaceFinder) updateInterfaces(interfaces []net.Interface) {
|
||||||
|
f.interfaces = interfaces
|
||||||
|
}
|
||||||
|
|
||||||
func (f *myInterfaceFinder) InterfaceIndexByName(name string) (interfaceIndex int, err error) {
|
func (f *myInterfaceFinder) InterfaceIndexByName(name string) (interfaceIndex int, err error) {
|
||||||
for _, netInterface := range f.ifs {
|
for _, netInterface := range f.interfaces {
|
||||||
if netInterface.Name == name {
|
if netInterface.Name == name {
|
||||||
return netInterface.Index, nil
|
return netInterface.Index, nil
|
||||||
}
|
}
|
||||||
@@ -36,7 +40,7 @@ func (f *myInterfaceFinder) InterfaceIndexByName(name string) (interfaceIndex in
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *myInterfaceFinder) InterfaceNameByIndex(index int) (interfaceName string, err error) {
|
func (f *myInterfaceFinder) InterfaceNameByIndex(index int) (interfaceName string, err error) {
|
||||||
for _, netInterface := range f.ifs {
|
for _, netInterface := range f.interfaces {
|
||||||
if netInterface.Index == index {
|
if netInterface.Index == index {
|
||||||
return netInterface.Name, nil
|
return netInterface.Name, nil
|
||||||
}
|
}
|
||||||
|
|||||||
463
route/router.go
463
route/router.go
@@ -2,29 +2,31 @@ package route
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||||
"github.com/sagernet/sing-box/common/geoip"
|
"github.com/sagernet/sing-box/common/geoip"
|
||||||
"github.com/sagernet/sing-box/common/geosite"
|
"github.com/sagernet/sing-box/common/geosite"
|
||||||
"github.com/sagernet/sing-box/common/mux"
|
"github.com/sagernet/sing-box/common/mux"
|
||||||
"github.com/sagernet/sing-box/common/process"
|
"github.com/sagernet/sing-box/common/process"
|
||||||
"github.com/sagernet/sing-box/common/sniff"
|
"github.com/sagernet/sing-box/common/sniff"
|
||||||
"github.com/sagernet/sing-box/common/warning"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/ntp"
|
"github.com/sagernet/sing-box/ntp"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/outbound"
|
"github.com/sagernet/sing-box/outbound"
|
||||||
"github.com/sagernet/sing-box/transport/fakeip"
|
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing-vmess"
|
"github.com/sagernet/sing-vmess"
|
||||||
@@ -36,30 +38,10 @@ import (
|
|||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
"github.com/sagernet/sing/common/uot"
|
"github.com/sagernet/sing/common/uot"
|
||||||
)
|
)
|
||||||
|
|
||||||
var warnDefaultInterfaceOnUnsupportedPlatform = warning.New(
|
|
||||||
func() bool {
|
|
||||||
return !(C.IsLinux || C.IsWindows || C.IsDarwin)
|
|
||||||
},
|
|
||||||
"route option `default_mark` is only supported on Linux and Windows",
|
|
||||||
)
|
|
||||||
|
|
||||||
var warnDefaultMarkOnNonLinux = warning.New(
|
|
||||||
func() bool {
|
|
||||||
return !C.IsLinux
|
|
||||||
},
|
|
||||||
"route option `default_mark` is only supported on Linux",
|
|
||||||
)
|
|
||||||
|
|
||||||
var warnFindProcessOnUnsupportedPlatform = warning.New(
|
|
||||||
func() bool {
|
|
||||||
return !(C.IsLinux || C.IsWindows || C.IsDarwin)
|
|
||||||
},
|
|
||||||
"route option `find_process` is only supported on Linux, Windows, and macOS",
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ adapter.Router = (*Router)(nil)
|
var _ adapter.Router = (*Router)(nil)
|
||||||
|
|
||||||
type Router struct {
|
type Router struct {
|
||||||
@@ -70,7 +52,6 @@ type Router struct {
|
|||||||
outbounds []adapter.Outbound
|
outbounds []adapter.Outbound
|
||||||
outboundByTag map[string]adapter.Outbound
|
outboundByTag map[string]adapter.Outbound
|
||||||
rules []adapter.Rule
|
rules []adapter.Rule
|
||||||
ipRules []adapter.IPRule
|
|
||||||
defaultDetour string
|
defaultDetour string
|
||||||
defaultOutboundForConnection adapter.Outbound
|
defaultOutboundForConnection adapter.Outbound
|
||||||
defaultOutboundForPacketConnection adapter.Outbound
|
defaultOutboundForPacketConnection adapter.Outbound
|
||||||
@@ -88,8 +69,6 @@ type Router struct {
|
|||||||
transports []dns.Transport
|
transports []dns.Transport
|
||||||
transportMap map[string]dns.Transport
|
transportMap map[string]dns.Transport
|
||||||
transportDomainStrategy map[dns.Transport]dns.DomainStrategy
|
transportDomainStrategy map[dns.Transport]dns.DomainStrategy
|
||||||
dnsReverseMapping *DNSReverseMapping
|
|
||||||
fakeIPStore adapter.FakeIPStore
|
|
||||||
interfaceFinder myInterfaceFinder
|
interfaceFinder myInterfaceFinder
|
||||||
autoDetectInterface bool
|
autoDetectInterface bool
|
||||||
defaultInterface string
|
defaultInterface string
|
||||||
@@ -113,23 +92,12 @@ func NewRouter(
|
|||||||
inbounds []option.Inbound,
|
inbounds []option.Inbound,
|
||||||
platformInterface platform.Interface,
|
platformInterface platform.Interface,
|
||||||
) (*Router, error) {
|
) (*Router, error) {
|
||||||
if options.DefaultInterface != "" {
|
|
||||||
warnDefaultInterfaceOnUnsupportedPlatform.Check()
|
|
||||||
}
|
|
||||||
if options.DefaultMark != 0 {
|
|
||||||
warnDefaultMarkOnNonLinux.Check()
|
|
||||||
}
|
|
||||||
if options.FindProcess {
|
|
||||||
warnFindProcessOnUnsupportedPlatform.Check()
|
|
||||||
}
|
|
||||||
|
|
||||||
router := &Router{
|
router := &Router{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
logger: logFactory.NewLogger("router"),
|
logger: logFactory.NewLogger("router"),
|
||||||
dnsLogger: logFactory.NewLogger("dns"),
|
dnsLogger: logFactory.NewLogger("dns"),
|
||||||
outboundByTag: make(map[string]adapter.Outbound),
|
outboundByTag: make(map[string]adapter.Outbound),
|
||||||
rules: make([]adapter.Rule, 0, len(options.Rules)),
|
rules: make([]adapter.Rule, 0, len(options.Rules)),
|
||||||
ipRules: make([]adapter.IPRule, 0, len(options.IPRules)),
|
|
||||||
dnsRules: make([]adapter.DNSRule, 0, len(dnsOptions.Rules)),
|
dnsRules: make([]adapter.DNSRule, 0, len(dnsOptions.Rules)),
|
||||||
needGeoIPDatabase: hasRule(options.Rules, isGeoIPRule) || hasDNSRule(dnsOptions.Rules, isGeoIPDNSRule),
|
needGeoIPDatabase: hasRule(options.Rules, isGeoIPRule) || hasDNSRule(dnsOptions.Rules, isGeoIPDNSRule),
|
||||||
needGeositeDatabase: hasRule(options.Rules, isGeositeRule) || hasDNSRule(dnsOptions.Rules, isGeositeDNSRule),
|
needGeositeDatabase: hasRule(options.Rules, isGeositeRule) || hasDNSRule(dnsOptions.Rules, isGeositeDNSRule),
|
||||||
@@ -151,13 +119,6 @@ func NewRouter(
|
|||||||
}
|
}
|
||||||
router.rules = append(router.rules, routeRule)
|
router.rules = append(router.rules, routeRule)
|
||||||
}
|
}
|
||||||
for i, ipRuleOptions := range options.IPRules {
|
|
||||||
ipRule, err := NewIPRule(router, router.logger, ipRuleOptions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "parse ip rule[", i, "]")
|
|
||||||
}
|
|
||||||
router.ipRules = append(router.ipRules, ipRule)
|
|
||||||
}
|
|
||||||
for i, dnsRuleOptions := range dnsOptions.Rules {
|
for i, dnsRuleOptions := range dnsOptions.Rules {
|
||||||
dnsRule, err := NewDNSRule(router, router.logger, dnsRuleOptions)
|
dnsRule, err := NewDNSRule(router, router.logger, dnsRuleOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -165,7 +126,6 @@ func NewRouter(
|
|||||||
}
|
}
|
||||||
router.dnsRules = append(router.dnsRules, dnsRule)
|
router.dnsRules = append(router.dnsRules, dnsRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
transports := make([]dns.Transport, len(dnsOptions.Servers))
|
transports := make([]dns.Transport, len(dnsOptions.Servers))
|
||||||
dummyTransportMap := make(map[string]dns.Transport)
|
dummyTransportMap := make(map[string]dns.Transport)
|
||||||
transportMap := make(map[string]dns.Transport)
|
transportMap := make(map[string]dns.Transport)
|
||||||
@@ -220,8 +180,12 @@ func NewRouter(
|
|||||||
} else {
|
} else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else if notIpAddress != nil && strings.Contains(server.Address, ".") {
|
} else if notIpAddress != nil {
|
||||||
return nil, E.New("parse dns server[", tag, "]: missing address_resolver")
|
switch serverURL.Scheme {
|
||||||
|
case "rcode", "dhcp":
|
||||||
|
default:
|
||||||
|
return nil, E.New("parse dns server[", tag, "]: missing address_resolver")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
transport, err := dns.CreateTransport(tag, ctx, logFactory.NewLogger(F.ToString("dns/transport[", tag, "]")), detour, server.Address)
|
transport, err := dns.CreateTransport(tag, ctx, logFactory.NewLogger(F.ToString("dns/transport[", tag, "]")), detour, server.Address)
|
||||||
@@ -273,45 +237,36 @@ func NewRouter(
|
|||||||
router.transportMap = transportMap
|
router.transportMap = transportMap
|
||||||
router.transportDomainStrategy = transportDomainStrategy
|
router.transportDomainStrategy = transportDomainStrategy
|
||||||
|
|
||||||
if dnsOptions.ReverseMapping {
|
usePlatformDefaultInterfaceMonitor := platformInterface != nil && platformInterface.UsePlatformDefaultInterfaceMonitor()
|
||||||
router.dnsReverseMapping = NewDNSReverseMapping()
|
needInterfaceMonitor := options.AutoDetectInterface || common.Any(inbounds, func(inbound option.Inbound) bool {
|
||||||
}
|
|
||||||
|
|
||||||
if fakeIPOptions := dnsOptions.FakeIP; fakeIPOptions != nil && dnsOptions.FakeIP.Enabled {
|
|
||||||
var inet4Range netip.Prefix
|
|
||||||
var inet6Range netip.Prefix
|
|
||||||
if fakeIPOptions.Inet4Range != nil {
|
|
||||||
inet4Range = fakeIPOptions.Inet4Range.Build()
|
|
||||||
}
|
|
||||||
if fakeIPOptions.Inet6Range != nil {
|
|
||||||
inet6Range = fakeIPOptions.Inet6Range.Build()
|
|
||||||
}
|
|
||||||
router.fakeIPStore = fakeip.NewStore(router, inet4Range, inet6Range)
|
|
||||||
}
|
|
||||||
|
|
||||||
needInterfaceMonitor := platformInterface == nil && (options.AutoDetectInterface || common.Any(inbounds, func(inbound option.Inbound) bool {
|
|
||||||
return inbound.HTTPOptions.SetSystemProxy || inbound.MixedOptions.SetSystemProxy || inbound.TunOptions.AutoRoute
|
return inbound.HTTPOptions.SetSystemProxy || inbound.MixedOptions.SetSystemProxy || inbound.TunOptions.AutoRoute
|
||||||
}))
|
})
|
||||||
|
|
||||||
if needInterfaceMonitor {
|
if needInterfaceMonitor {
|
||||||
networkMonitor, err := tun.NewNetworkUpdateMonitor(router)
|
if !usePlatformDefaultInterfaceMonitor {
|
||||||
if err == nil {
|
networkMonitor, err := tun.NewNetworkUpdateMonitor(router)
|
||||||
router.networkMonitor = networkMonitor
|
if err != os.ErrInvalid {
|
||||||
networkMonitor.RegisterCallback(router.interfaceFinder.update)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
router.networkMonitor = networkMonitor
|
||||||
|
networkMonitor.RegisterCallback(router.interfaceFinder.update)
|
||||||
|
interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, tun.DefaultInterfaceMonitorOptions{
|
||||||
|
OverrideAndroidVPN: options.OverrideAndroidVPN,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.New("auto_detect_interface unsupported on current platform")
|
||||||
|
}
|
||||||
|
interfaceMonitor.RegisterCallback(router.notifyNetworkUpdate)
|
||||||
|
router.interfaceMonitor = interfaceMonitor
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
interfaceMonitor := platformInterface.CreateDefaultInterfaceMonitor(router)
|
||||||
|
interfaceMonitor.RegisterCallback(router.notifyNetworkUpdate)
|
||||||
|
router.interfaceMonitor = interfaceMonitor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if router.networkMonitor != nil && needInterfaceMonitor {
|
|
||||||
interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, tun.DefaultInterfaceMonitorOptions{
|
|
||||||
OverrideAndroidVPN: options.OverrideAndroidVPN,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.New("auto_detect_interface unsupported on current platform")
|
|
||||||
}
|
|
||||||
interfaceMonitor.RegisterCallback(router.notifyNetworkUpdate)
|
|
||||||
router.interfaceMonitor = interfaceMonitor
|
|
||||||
}
|
|
||||||
|
|
||||||
needFindProcess := hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess
|
needFindProcess := hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess
|
||||||
needPackageManager := C.IsAndroid && platformInterface == nil && (needFindProcess || common.Any(inbounds, func(inbound option.Inbound) bool {
|
needPackageManager := C.IsAndroid && platformInterface == nil && (needFindProcess || common.Any(inbounds, func(inbound option.Inbound) bool {
|
||||||
return len(inbound.TunOptions.IncludePackage) > 0 || len(inbound.TunOptions.ExcludePackage) > 0
|
return len(inbound.TunOptions.IncludePackage) > 0 || len(inbound.TunOptions.ExcludePackage) > 0
|
||||||
@@ -495,12 +450,6 @@ func (r *Router) Start() error {
|
|||||||
return E.Cause(err, "initialize DNS rule[", i, "]")
|
return E.Cause(err, "initialize DNS rule[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if r.fakeIPStore != nil {
|
|
||||||
err := r.fakeIPStore.Start()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, transport := range r.transports {
|
for i, transport := range r.transports {
|
||||||
err := transport.Start()
|
err := transport.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -566,15 +515,30 @@ func (r *Router) Close() error {
|
|||||||
return E.Cause(err, "close time service")
|
return E.Cause(err, "close time service")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if r.fakeIPStore != nil {
|
|
||||||
r.logger.Trace("closing fakeip store")
|
|
||||||
err = E.Append(err, r.fakeIPStore.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close fakeip store")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Router) GeoIPReader() *geoip.Reader {
|
||||||
|
return r.geoIPReader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) LoadGeosite(code string) (adapter.Rule, error) {
|
||||||
|
rule, cached := r.geositeCache[code]
|
||||||
|
if cached {
|
||||||
|
return rule, nil
|
||||||
|
}
|
||||||
|
items, err := r.geositeReader.Read(code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rule, err = NewDefaultRule(r, nil, geosite.Compile(items))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.geositeCache[code] = rule
|
||||||
|
return rule, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Router) Outbound(tag string) (adapter.Outbound, bool) {
|
func (r *Router) Outbound(tag string) (adapter.Outbound, bool) {
|
||||||
outbound, loaded := r.outboundByTag[tag]
|
outbound, loaded := r.outboundByTag[tag]
|
||||||
return outbound, loaded
|
return outbound, loaded
|
||||||
@@ -588,10 +552,6 @@ func (r *Router) DefaultOutbound(network string) adapter.Outbound {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) FakeIPStore() adapter.FakeIPStore {
|
|
||||||
return r.fakeIPStore
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
if metadata.InboundDetour != "" {
|
if metadata.InboundDetour != "" {
|
||||||
if metadata.LastInbound == metadata.InboundDetour {
|
if metadata.LastInbound == metadata.InboundDetour {
|
||||||
@@ -644,19 +604,6 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}
|
metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}
|
||||||
return r.RoutePacketConnection(ctx, uot.NewConn(conn, uot.Request{}), metadata)
|
return r.RoutePacketConnection(ctx, uot.NewConn(conn, uot.Request{}), metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) {
|
|
||||||
domain, loaded := r.fakeIPStore.Lookup(metadata.Destination.Addr)
|
|
||||||
if !loaded {
|
|
||||||
return E.New("missing fakeip context")
|
|
||||||
}
|
|
||||||
metadata.Destination = M.Socksaddr{
|
|
||||||
Fqdn: domain,
|
|
||||||
Port: metadata.Destination.Port,
|
|
||||||
}
|
|
||||||
r.logger.DebugContext(ctx, "found fakeip domain: ", domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
if metadata.InboundOptions.SniffEnabled {
|
if metadata.InboundOptions.SniffEnabled {
|
||||||
buffer := buf.NewPacket()
|
buffer := buf.NewPacket()
|
||||||
buffer.FullReset()
|
buffer.FullReset()
|
||||||
@@ -684,15 +631,6 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
buffer.Release()
|
buffer.Release()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.dnsReverseMapping != nil && metadata.Domain == "" {
|
|
||||||
domain, loaded := r.dnsReverseMapping.Query(metadata.Destination.Addr)
|
|
||||||
if loaded {
|
|
||||||
metadata.Domain = domain
|
|
||||||
r.logger.DebugContext(ctx, "found reserve mapped domain: ", metadata.Domain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if metadata.Destination.IsFqdn() && dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS {
|
if metadata.Destination.IsFqdn() && dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS {
|
||||||
addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, dns.DomainStrategy(metadata.InboundOptions.DomainStrategy))
|
addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, dns.DomainStrategy(metadata.InboundOptions.DomainStrategy))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -747,21 +685,6 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
metadata.Network = N.NetworkUDP
|
metadata.Network = N.NetworkUDP
|
||||||
|
|
||||||
var originAddress M.Socksaddr
|
|
||||||
if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) {
|
|
||||||
domain, loaded := r.fakeIPStore.Lookup(metadata.Destination.Addr)
|
|
||||||
if !loaded {
|
|
||||||
return E.New("missing fakeip context")
|
|
||||||
}
|
|
||||||
originAddress = metadata.Destination
|
|
||||||
metadata.Destination = M.Socksaddr{
|
|
||||||
Fqdn: domain,
|
|
||||||
Port: metadata.Destination.Port,
|
|
||||||
}
|
|
||||||
r.logger.DebugContext(ctx, "found fakeip domain: ", domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
if metadata.InboundOptions.SniffEnabled {
|
if metadata.InboundOptions.SniffEnabled {
|
||||||
buffer := buf.NewPacket()
|
buffer := buf.NewPacket()
|
||||||
buffer.FullReset()
|
buffer.FullReset()
|
||||||
@@ -788,13 +711,6 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||||||
}
|
}
|
||||||
conn = bufio.NewCachedPacketConn(conn, buffer, destination)
|
conn = bufio.NewCachedPacketConn(conn, buffer, destination)
|
||||||
}
|
}
|
||||||
if r.dnsReverseMapping != nil && metadata.Domain == "" {
|
|
||||||
domain, loaded := r.dnsReverseMapping.Query(metadata.Destination.Addr)
|
|
||||||
if loaded {
|
|
||||||
metadata.Domain = domain
|
|
||||||
r.logger.DebugContext(ctx, "found reserve mapped domain: ", metadata.Domain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsFqdn() && dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS {
|
if metadata.Destination.IsFqdn() && dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS {
|
||||||
addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, dns.DomainStrategy(metadata.InboundOptions.DomainStrategy))
|
addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, dns.DomainStrategy(metadata.InboundOptions.DomainStrategy))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -820,9 +736,6 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||||||
conn = statsService.RoutedPacketConnection(metadata.Inbound, detour.Tag(), metadata.User, conn)
|
conn = statsService.RoutedPacketConnection(metadata.Inbound, detour.Tag(), metadata.User, conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if originAddress.IsValid() {
|
|
||||||
conn = fakeip.NewNATPacketConn(conn, originAddress, metadata.Destination)
|
|
||||||
}
|
|
||||||
return detour.NewPacketConnection(ctx, conn, metadata)
|
return detour.NewPacketConnection(ctx, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -886,12 +799,31 @@ func (r *Router) InterfaceFinder() control.InterfaceFinder {
|
|||||||
return &r.interfaceFinder
|
return &r.interfaceFinder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Router) UpdateInterfaces() error {
|
||||||
|
if r.platformInterface == nil || !r.platformInterface.UsePlatformInterfaceGetter() {
|
||||||
|
return r.interfaceFinder.update()
|
||||||
|
} else {
|
||||||
|
interfaces, err := r.platformInterface.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.interfaceFinder.updateInterfaces(common.Map(interfaces, func(it platform.NetworkInterface) net.Interface {
|
||||||
|
return net.Interface{
|
||||||
|
Name: it.Name,
|
||||||
|
Index: it.Index,
|
||||||
|
MTU: it.MTU,
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Router) AutoDetectInterface() bool {
|
func (r *Router) AutoDetectInterface() bool {
|
||||||
return r.autoDetectInterface
|
return r.autoDetectInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) AutoDetectInterfaceFunc() control.Func {
|
func (r *Router) AutoDetectInterfaceFunc() control.Func {
|
||||||
if r.platformInterface != nil {
|
if r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() {
|
||||||
return r.platformInterface.AutoDetectInterfaceControl()
|
return r.platformInterface.AutoDetectInterfaceControl()
|
||||||
} else {
|
} else {
|
||||||
return control.BindToInterfaceFunc(r.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int) {
|
return control.BindToInterfaceFunc(r.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int) {
|
||||||
@@ -917,10 +849,6 @@ func (r *Router) Rules() []adapter.Rule {
|
|||||||
return r.rules
|
return r.rules
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) IPRules() []adapter.IPRule {
|
|
||||||
return r.ipRules
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) NetworkMonitor() tun.NetworkUpdateMonitor {
|
func (r *Router) NetworkMonitor() tun.NetworkUpdateMonitor {
|
||||||
return r.networkMonitor
|
return r.networkMonitor
|
||||||
}
|
}
|
||||||
@@ -956,6 +884,239 @@ func (r *Router) SetV2RayServer(server adapter.V2RayServer) {
|
|||||||
r.v2rayServer = server
|
r.v2rayServer = server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {
|
||||||
|
for _, rule := range rules {
|
||||||
|
switch rule.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
if cond(rule.DefaultOptions) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
for _, subRule := range rule.LogicalOptions.Rules {
|
||||||
|
if cond(subRule) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bool) bool {
|
||||||
|
for _, rule := range rules {
|
||||||
|
switch rule.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
if cond(rule.DefaultOptions) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
for _, subRule := range rule.LogicalOptions.Rules {
|
||||||
|
if cond(subRule) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGeoIPRule(rule option.DefaultRule) bool {
|
||||||
|
return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGeoIPDNSRule(rule option.DefaultDNSRule) bool {
|
||||||
|
return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGeositeRule(rule option.DefaultRule) bool {
|
||||||
|
return len(rule.Geosite) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGeositeDNSRule(rule option.DefaultDNSRule) bool {
|
||||||
|
return len(rule.Geosite) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func isProcessRule(rule option.DefaultRule) bool {
|
||||||
|
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func isProcessDNSRule(rule option.DefaultDNSRule) bool {
|
||||||
|
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func notPrivateNode(code string) bool {
|
||||||
|
return code != "private"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) prepareGeoIPDatabase() error {
|
||||||
|
var geoPath string
|
||||||
|
if r.geoIPOptions.Path != "" {
|
||||||
|
geoPath = r.geoIPOptions.Path
|
||||||
|
} else {
|
||||||
|
geoPath = "geoip.db"
|
||||||
|
if foundPath, loaded := C.FindPath(geoPath); loaded {
|
||||||
|
geoPath = foundPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
geoPath = C.BasePath(geoPath)
|
||||||
|
if !rw.FileExists(geoPath) {
|
||||||
|
r.logger.Warn("geoip database not exists: ", geoPath)
|
||||||
|
var err error
|
||||||
|
for attempts := 0; attempts < 3; attempts++ {
|
||||||
|
err = r.downloadGeoIPDatabase(geoPath)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
r.logger.Error("download geoip database: ", err)
|
||||||
|
os.Remove(geoPath)
|
||||||
|
// time.Sleep(10 * time.Second)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
geoReader, codes, err := geoip.Open(geoPath)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "open geoip database")
|
||||||
|
}
|
||||||
|
r.logger.Info("loaded geoip database: ", len(codes), " codes")
|
||||||
|
r.geoIPReader = geoReader
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) prepareGeositeDatabase() error {
|
||||||
|
var geoPath string
|
||||||
|
if r.geositeOptions.Path != "" {
|
||||||
|
geoPath = r.geositeOptions.Path
|
||||||
|
} else {
|
||||||
|
geoPath = "geosite.db"
|
||||||
|
if foundPath, loaded := C.FindPath(geoPath); loaded {
|
||||||
|
geoPath = foundPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
geoPath = C.BasePath(geoPath)
|
||||||
|
if !rw.FileExists(geoPath) {
|
||||||
|
r.logger.Warn("geosite database not exists: ", geoPath)
|
||||||
|
var err error
|
||||||
|
for attempts := 0; attempts < 3; attempts++ {
|
||||||
|
err = r.downloadGeositeDatabase(geoPath)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
r.logger.Error("download geosite database: ", err)
|
||||||
|
os.Remove(geoPath)
|
||||||
|
// time.Sleep(10 * time.Second)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
geoReader, codes, err := geosite.Open(geoPath)
|
||||||
|
if err == nil {
|
||||||
|
r.logger.Info("loaded geosite database: ", len(codes), " codes")
|
||||||
|
r.geositeReader = geoReader
|
||||||
|
} else {
|
||||||
|
return E.Cause(err, "open geosite database")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) downloadGeoIPDatabase(savePath string) error {
|
||||||
|
var downloadURL string
|
||||||
|
if r.geoIPOptions.DownloadURL != "" {
|
||||||
|
downloadURL = r.geoIPOptions.DownloadURL
|
||||||
|
} else {
|
||||||
|
downloadURL = "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db"
|
||||||
|
}
|
||||||
|
r.logger.Info("downloading geoip database")
|
||||||
|
var detour adapter.Outbound
|
||||||
|
if r.geoIPOptions.DownloadDetour != "" {
|
||||||
|
outbound, loaded := r.Outbound(r.geoIPOptions.DownloadDetour)
|
||||||
|
if !loaded {
|
||||||
|
return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour)
|
||||||
|
}
|
||||||
|
detour = outbound
|
||||||
|
} else {
|
||||||
|
detour = r.defaultOutboundForConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
||||||
|
os.MkdirAll(parentDir, 0o755)
|
||||||
|
}
|
||||||
|
|
||||||
|
saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "open output file: ", downloadURL)
|
||||||
|
}
|
||||||
|
defer saveFile.Close()
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
TLSHandshakeTimeout: 5 * time.Second,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
defer httpClient.CloseIdleConnections()
|
||||||
|
response, err := httpClient.Get(downloadURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
_, err = io.Copy(saveFile, response.Body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) downloadGeositeDatabase(savePath string) error {
|
||||||
|
var downloadURL string
|
||||||
|
if r.geositeOptions.DownloadURL != "" {
|
||||||
|
downloadURL = r.geositeOptions.DownloadURL
|
||||||
|
} else {
|
||||||
|
downloadURL = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db"
|
||||||
|
}
|
||||||
|
r.logger.Info("downloading geosite database")
|
||||||
|
var detour adapter.Outbound
|
||||||
|
if r.geositeOptions.DownloadDetour != "" {
|
||||||
|
outbound, loaded := r.Outbound(r.geositeOptions.DownloadDetour)
|
||||||
|
if !loaded {
|
||||||
|
return E.New("detour outbound not found: ", r.geositeOptions.DownloadDetour)
|
||||||
|
}
|
||||||
|
detour = outbound
|
||||||
|
} else {
|
||||||
|
detour = r.defaultOutboundForConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
||||||
|
os.MkdirAll(parentDir, 0o755)
|
||||||
|
}
|
||||||
|
|
||||||
|
saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "open output file: ", downloadURL)
|
||||||
|
}
|
||||||
|
defer saveFile.Close()
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
TLSHandshakeTimeout: 5 * time.Second,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
defer httpClient.CloseIdleConnections()
|
||||||
|
response, err := httpClient.Get(downloadURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
_, err = io.Copy(saveFile, response.Body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Router) OnPackagesUpdated(packages int, sharedUsers int) {
|
func (r *Router) OnPackagesUpdated(packages int, sharedUsers int) {
|
||||||
r.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users")
|
r.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users")
|
||||||
}
|
}
|
||||||
@@ -970,7 +1131,7 @@ func (r *Router) NewError(ctx context.Context, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) notifyNetworkUpdate(int) error {
|
func (r *Router) notifyNetworkUpdate(int) error {
|
||||||
if C.IsAndroid {
|
if C.IsAndroid && r.platformInterface == nil {
|
||||||
var vpnStatus string
|
var vpnStatus string
|
||||||
if r.interfaceMonitor.AndroidVPNEnabled() {
|
if r.interfaceMonitor.AndroidVPNEnabled() {
|
||||||
vpnStatus = "enabled"
|
vpnStatus = "enabled"
|
||||||
@@ -982,6 +1143,10 @@ func (r *Router) notifyNetworkUpdate(int) error {
|
|||||||
r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()))
|
r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if conntrack.Enabled {
|
||||||
|
conntrack.Close()
|
||||||
|
}
|
||||||
|
|
||||||
for _, outbound := range r.outbounds {
|
for _, outbound := range r.outbounds {
|
||||||
listener, isListener := outbound.(adapter.InterfaceUpdateListener)
|
listener, isListener := outbound.(adapter.InterfaceUpdateListener)
|
||||||
if isListener {
|
if isListener {
|
||||||
|
|||||||
@@ -4,39 +4,17 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing/common/cache"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
mDNS "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DNSReverseMapping struct {
|
|
||||||
cache *cache.LruCache[netip.Addr, string]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDNSReverseMapping() *DNSReverseMapping {
|
|
||||||
return &DNSReverseMapping{
|
|
||||||
cache: cache.New[netip.Addr, string](),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *DNSReverseMapping) Save(address netip.Addr, domain string, ttl int) {
|
|
||||||
m.cache.StoreWithExpire(address, domain, time.Now().Add(time.Duration(ttl)*time.Second))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *DNSReverseMapping) Query(address netip.Addr) (string, bool) {
|
|
||||||
domain, loaded := m.cache.Load(address)
|
|
||||||
return domain, loaded
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) matchDNS(ctx context.Context) (context.Context, dns.Transport, dns.DomainStrategy) {
|
func (r *Router) matchDNS(ctx context.Context) (context.Context, dns.Transport, dns.DomainStrategy) {
|
||||||
metadata := adapter.ContextFrom(ctx)
|
metadata := adapter.ContextFrom(ctx)
|
||||||
if metadata == nil {
|
if metadata == nil {
|
||||||
@@ -47,9 +25,6 @@ func (r *Router) matchDNS(ctx context.Context) (context.Context, dns.Transport,
|
|||||||
if rule.DisableCache() {
|
if rule.DisableCache() {
|
||||||
ctx = dns.ContextWithDisableCache(ctx, true)
|
ctx = dns.ContextWithDisableCache(ctx, true)
|
||||||
}
|
}
|
||||||
if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil {
|
|
||||||
ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL)
|
|
||||||
}
|
|
||||||
detour := rule.Outbound()
|
detour := rule.Outbound()
|
||||||
r.dnsLogger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour)
|
r.dnsLogger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour)
|
||||||
if transport, loaded := r.transportMap[detour]; loaded {
|
if transport, loaded := r.transportMap[detour]; loaded {
|
||||||
@@ -94,16 +69,6 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er
|
|||||||
if len(message.Question) > 0 && response != nil {
|
if len(message.Question) > 0 && response != nil {
|
||||||
LogDNSAnswers(r.dnsLogger, ctx, message.Question[0].Name, response.Answer)
|
LogDNSAnswers(r.dnsLogger, ctx, message.Question[0].Name, response.Answer)
|
||||||
}
|
}
|
||||||
if r.dnsReverseMapping != nil && len(message.Question) > 0 && response != nil && len(response.Answer) > 0 {
|
|
||||||
for _, answer := range response.Answer {
|
|
||||||
switch record := answer.(type) {
|
|
||||||
case *mDNS.A:
|
|
||||||
r.dnsReverseMapping.Save(M.AddrFromIP(record.A), fqdnToDomain(record.Hdr.Name), int(record.Hdr.Ttl))
|
|
||||||
case *mDNS.AAAA:
|
|
||||||
r.dnsReverseMapping.Save(M.AddrFromIP(record.AAAA), fqdnToDomain(record.Hdr.Name), int(record.Hdr.Ttl))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,283 +0,0 @@
|
|||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/geoip"
|
|
||||||
"github.com/sagernet/sing-box/common/geosite"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
"github.com/sagernet/sing/common/rw"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r *Router) GeoIPReader() *geoip.Reader {
|
|
||||||
return r.geoIPReader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) LoadGeosite(code string) (adapter.Rule, error) {
|
|
||||||
rule, cached := r.geositeCache[code]
|
|
||||||
if cached {
|
|
||||||
return rule, nil
|
|
||||||
}
|
|
||||||
items, err := r.geositeReader.Read(code)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rule, err = NewDefaultRule(r, nil, geosite.Compile(items))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r.geositeCache[code] = rule
|
|
||||||
return rule, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) prepareGeoIPDatabase() error {
|
|
||||||
var geoPath string
|
|
||||||
if r.geoIPOptions.Path != "" {
|
|
||||||
geoPath = r.geoIPOptions.Path
|
|
||||||
} else {
|
|
||||||
geoPath = "geoip.db"
|
|
||||||
if foundPath, loaded := C.FindPath(geoPath); loaded {
|
|
||||||
geoPath = foundPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
geoPath = C.BasePath(geoPath)
|
|
||||||
if rw.FileExists(geoPath) {
|
|
||||||
geoReader, codes, err := geoip.Open(geoPath)
|
|
||||||
if err == nil {
|
|
||||||
r.logger.Info("loaded geoip database: ", len(codes), " codes")
|
|
||||||
r.geoIPReader = geoReader
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !rw.FileExists(geoPath) {
|
|
||||||
r.logger.Warn("geoip database not exists: ", geoPath)
|
|
||||||
var err error
|
|
||||||
for attempts := 0; attempts < 3; attempts++ {
|
|
||||||
err = r.downloadGeoIPDatabase(geoPath)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
r.logger.Error("download geoip database: ", err)
|
|
||||||
os.Remove(geoPath)
|
|
||||||
// time.Sleep(10 * time.Second)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
geoReader, codes, err := geoip.Open(geoPath)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "open geoip database")
|
|
||||||
}
|
|
||||||
r.logger.Info("loaded geoip database: ", len(codes), " codes")
|
|
||||||
r.geoIPReader = geoReader
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) prepareGeositeDatabase() error {
|
|
||||||
var geoPath string
|
|
||||||
if r.geositeOptions.Path != "" {
|
|
||||||
geoPath = r.geositeOptions.Path
|
|
||||||
} else {
|
|
||||||
geoPath = "geosite.db"
|
|
||||||
if foundPath, loaded := C.FindPath(geoPath); loaded {
|
|
||||||
geoPath = foundPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
geoPath = C.BasePath(geoPath)
|
|
||||||
if !rw.FileExists(geoPath) {
|
|
||||||
r.logger.Warn("geosite database not exists: ", geoPath)
|
|
||||||
var err error
|
|
||||||
for attempts := 0; attempts < 3; attempts++ {
|
|
||||||
err = r.downloadGeositeDatabase(geoPath)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
r.logger.Error("download geosite database: ", err)
|
|
||||||
os.Remove(geoPath)
|
|
||||||
// time.Sleep(10 * time.Second)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
geoReader, codes, err := geosite.Open(geoPath)
|
|
||||||
if err == nil {
|
|
||||||
r.logger.Info("loaded geosite database: ", len(codes), " codes")
|
|
||||||
r.geositeReader = geoReader
|
|
||||||
} else {
|
|
||||||
return E.Cause(err, "open geosite database")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) downloadGeoIPDatabase(savePath string) error {
|
|
||||||
var downloadURL string
|
|
||||||
if r.geoIPOptions.DownloadURL != "" {
|
|
||||||
downloadURL = r.geoIPOptions.DownloadURL
|
|
||||||
} else {
|
|
||||||
downloadURL = "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db"
|
|
||||||
}
|
|
||||||
r.logger.Info("downloading geoip database")
|
|
||||||
var detour adapter.Outbound
|
|
||||||
if r.geoIPOptions.DownloadDetour != "" {
|
|
||||||
outbound, loaded := r.Outbound(r.geoIPOptions.DownloadDetour)
|
|
||||||
if !loaded {
|
|
||||||
return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour)
|
|
||||||
}
|
|
||||||
detour = outbound
|
|
||||||
} else {
|
|
||||||
detour = r.defaultOutboundForConnection
|
|
||||||
}
|
|
||||||
|
|
||||||
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
|
||||||
os.MkdirAll(parentDir, 0o755)
|
|
||||||
}
|
|
||||||
|
|
||||||
saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "open output file: ", downloadURL)
|
|
||||||
}
|
|
||||||
defer saveFile.Close()
|
|
||||||
|
|
||||||
httpClient := &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
TLSHandshakeTimeout: 5 * time.Second,
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
defer httpClient.CloseIdleConnections()
|
|
||||||
response, err := httpClient.Get(downloadURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
_, err = io.Copy(saveFile, response.Body)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) downloadGeositeDatabase(savePath string) error {
|
|
||||||
var downloadURL string
|
|
||||||
if r.geositeOptions.DownloadURL != "" {
|
|
||||||
downloadURL = r.geositeOptions.DownloadURL
|
|
||||||
} else {
|
|
||||||
downloadURL = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db"
|
|
||||||
}
|
|
||||||
r.logger.Info("downloading geosite database")
|
|
||||||
var detour adapter.Outbound
|
|
||||||
if r.geositeOptions.DownloadDetour != "" {
|
|
||||||
outbound, loaded := r.Outbound(r.geositeOptions.DownloadDetour)
|
|
||||||
if !loaded {
|
|
||||||
return E.New("detour outbound not found: ", r.geositeOptions.DownloadDetour)
|
|
||||||
}
|
|
||||||
detour = outbound
|
|
||||||
} else {
|
|
||||||
detour = r.defaultOutboundForConnection
|
|
||||||
}
|
|
||||||
|
|
||||||
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
|
||||||
os.MkdirAll(parentDir, 0o755)
|
|
||||||
}
|
|
||||||
|
|
||||||
saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "open output file: ", downloadURL)
|
|
||||||
}
|
|
||||||
defer saveFile.Close()
|
|
||||||
|
|
||||||
httpClient := &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
TLSHandshakeTimeout: 5 * time.Second,
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
defer httpClient.CloseIdleConnections()
|
|
||||||
response, err := httpClient.Get(downloadURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
_, err = io.Copy(saveFile, response.Body)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {
|
|
||||||
for _, rule := range rules {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
if cond(rule.DefaultOptions) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
for _, subRule := range rule.LogicalOptions.Rules {
|
|
||||||
if cond(subRule) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bool) bool {
|
|
||||||
for _, rule := range rules {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
if cond(rule.DefaultOptions) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
for _, subRule := range rule.LogicalOptions.Rules {
|
|
||||||
if cond(subRule) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGeoIPRule(rule option.DefaultRule) bool {
|
|
||||||
return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGeoIPDNSRule(rule option.DefaultDNSRule) bool {
|
|
||||||
return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGeositeRule(rule option.DefaultRule) bool {
|
|
||||||
return len(rule.Geosite) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGeositeDNSRule(rule option.DefaultDNSRule) bool {
|
|
||||||
return len(rule.Geosite) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func isProcessRule(rule option.DefaultRule) bool {
|
|
||||||
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func isProcessDNSRule(rule option.DefaultDNSRule) bool {
|
|
||||||
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func notPrivateNode(code string) bool {
|
|
||||||
return code != "private"
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r *Router) RouteIPConnection(ctx context.Context, conn tun.RouteContext, metadata adapter.InboundContext) tun.RouteAction {
|
|
||||||
if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) {
|
|
||||||
domain, loaded := r.fakeIPStore.Lookup(metadata.Destination.Addr)
|
|
||||||
if !loaded {
|
|
||||||
r.logger.ErrorContext(ctx, "missing fakeip context")
|
|
||||||
return (*tun.ActionReturn)(nil)
|
|
||||||
}
|
|
||||||
metadata.Destination = M.Socksaddr{
|
|
||||||
Fqdn: domain,
|
|
||||||
Port: metadata.Destination.Port,
|
|
||||||
}
|
|
||||||
r.logger.DebugContext(ctx, "found fakeip domain: ", domain)
|
|
||||||
}
|
|
||||||
if r.dnsReverseMapping != nil && metadata.Domain == "" {
|
|
||||||
domain, loaded := r.dnsReverseMapping.Query(metadata.Destination.Addr)
|
|
||||||
if loaded {
|
|
||||||
metadata.Domain = domain
|
|
||||||
r.logger.DebugContext(ctx, "found reserve mapped domain: ", metadata.Domain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsFqdn() && dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS {
|
|
||||||
addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, dns.DomainStrategy(metadata.InboundOptions.DomainStrategy))
|
|
||||||
if err != nil {
|
|
||||||
r.logger.ErrorContext(ctx, err)
|
|
||||||
return (*tun.ActionReturn)(nil)
|
|
||||||
}
|
|
||||||
metadata.DestinationAddresses = addresses
|
|
||||||
r.dnsLogger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]")
|
|
||||||
}
|
|
||||||
for i, rule := range r.ipRules {
|
|
||||||
if rule.Match(&metadata) {
|
|
||||||
if rule.Action() == tun.ActionTypeBlock {
|
|
||||||
r.logger.InfoContext(ctx, "match[", i, "] ", rule.String(), " => block")
|
|
||||||
return (*tun.ActionBlock)(nil)
|
|
||||||
}
|
|
||||||
detour := rule.Outbound()
|
|
||||||
r.logger.InfoContext(ctx, "match[", i, "] ", rule.String(), " => ", detour)
|
|
||||||
outbound, loaded := r.Outbound(detour)
|
|
||||||
if !loaded {
|
|
||||||
r.logger.ErrorContext(ctx, "outbound not found: ", detour)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ipOutbound, loaded := outbound.(adapter.IPOutbound)
|
|
||||||
if !loaded {
|
|
||||||
r.logger.ErrorContext(ctx, "outbound have no ip connection support: ", detour)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
destination, err := ipOutbound.NewIPConnection(ctx, conn, metadata)
|
|
||||||
if err != nil {
|
|
||||||
r.logger.ErrorContext(ctx, err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return &tun.ActionDirect{DirectDestination: destination}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (*tun.ActionReturn)(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) NatRequired(outbound string) bool {
|
|
||||||
for _, ipRule := range r.ipRules {
|
|
||||||
if ipRule.Outbound() == outbound {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user