mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-09-10 13:24:08 +08:00
Compare commits
11 Commits
b03359ec47
...
a4c600d94a
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a4c600d94a | ||
![]() |
900a2c1ef1 | ||
![]() |
4b3861232b | ||
![]() |
31a0bc94ae | ||
![]() |
8c1d1e9b0e | ||
![]() |
735b52fee5 | ||
![]() |
9fc9dccf46 | ||
![]() |
c64f7fd8a1 | ||
![]() |
de10bb00a9 | ||
![]() |
fdc181106d | ||
![]() |
8752b631bd |
@ -2,6 +2,7 @@ package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
@ -18,6 +19,11 @@ type Outbound interface {
|
||||
N.Dialer
|
||||
}
|
||||
|
||||
type OutboundWithPreferredRoutes interface {
|
||||
PreferredDomain(domain string) bool
|
||||
PreferredAddress(address netip.Addr) bool
|
||||
}
|
||||
|
||||
type OutboundRegistry interface {
|
||||
option.OutboundOptionsRegistry
|
||||
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
||||
|
@ -2,6 +2,18 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.13.0-alpha.2
|
||||
|
||||
* Add `preferred_by` rule item **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
The new `preferred_by` routing rule item allows you to
|
||||
match preferred domains and addresses for specific outbounds.
|
||||
|
||||
See [Route Rule](/configuration/route/rule/#preferred_by).
|
||||
|
||||
#### 1.13.0-alpha.1
|
||||
|
||||
* Add interface address rule items **1**
|
||||
@ -14,7 +26,7 @@ New interface address rules allow you to dynamically adjust rules based on your
|
||||
See [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/)
|
||||
and [Headless Rule](/configuration/rule-set/headless-rule/).
|
||||
|
||||
### 1.12.1
|
||||
#### 1.12.1
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@ -169,7 +181,7 @@ We continue to experience issues updating our sing-box apps on the App Store and
|
||||
Until we rewrite and resubmit the apps, they are considered irrecoverable.
|
||||
Therefore, after this release, we will not be repeating this notice unless there is new information.
|
||||
|
||||
### 1.11.15
|
||||
#### 1.11.15
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@ -185,7 +197,7 @@ violated the rules (TestFlight users are not affected)._
|
||||
|
||||
We have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack.
|
||||
|
||||
### 1.11.14
|
||||
#### 1.11.14
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@ -235,7 +247,7 @@ You can now choose what the DERP home page shows, just like with derper's `-home
|
||||
|
||||
See [DERP](/configuration/service/derp/#home).
|
||||
|
||||
### 1.11.13
|
||||
#### 1.11.13
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@ -273,7 +285,7 @@ SSM API service is a RESTful API server for managing Shadowsocks servers.
|
||||
|
||||
See [SSM API Service](/configuration/service/ssm-api/).
|
||||
|
||||
### 1.11.11
|
||||
#### 1.11.11
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@ -305,7 +317,7 @@ You can now set `bind_interface`, `routing_mark` and `reuse_addr` in Listen Fiel
|
||||
|
||||
See [Listen Fields](/configuration/shared/listen/).
|
||||
|
||||
### 1.11.10
|
||||
#### 1.11.10
|
||||
|
||||
* Undeprecate the `block` outbound **1**
|
||||
* Fixes and improvements
|
||||
@ -323,7 +335,7 @@ violated the rules (TestFlight users are not affected)._
|
||||
* Update quic-go to v0.51.0
|
||||
* Fixes and improvements
|
||||
|
||||
### 1.11.9
|
||||
#### 1.11.9
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@ -334,7 +346,7 @@ violated the rules (TestFlight users are not affected)._
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
### 1.11.8
|
||||
#### 1.11.8
|
||||
|
||||
* Improve `auto_redirect` **1**
|
||||
* Fixes and improvements
|
||||
@ -351,7 +363,7 @@ violated the rules (TestFlight users are not affected)._
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
### 1.11.7
|
||||
#### 1.11.7
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@ -367,7 +379,7 @@ violated the rules (TestFlight users are not affected)._
|
||||
Now `auto_redirect` fixes compatibility issues between tun and Docker bridge networks,
|
||||
see [Tun](/configuration/inbound/tun/#auto_redirect).
|
||||
|
||||
### 1.11.6
|
||||
#### 1.11.6
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@ -408,7 +420,7 @@ See [Protocol Sniff](/configuration/route/sniff/).
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/#domain_resolver).
|
||||
|
||||
### 1.11.5
|
||||
#### 1.11.5
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@ -424,7 +436,7 @@ violated the rules (TestFlight users are not affected)._
|
||||
|
||||
See [DNS Rule Action](/configuration/dns/rule_action/#predefined).
|
||||
|
||||
### 1.11.4
|
||||
#### 1.11.4
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@ -480,7 +492,7 @@ Due to maintenance difficulties, sing-box 1.12.0 requires at least Go 1.23 to co
|
||||
|
||||
For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches from [MetaCubeX/go](https://github.com/MetaCubeX/go).
|
||||
|
||||
### 1.11.3
|
||||
#### 1.11.3
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@ -491,7 +503,7 @@ process._
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
### 1.11.1
|
||||
#### 1.11.1
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@ -670,7 +682,7 @@ See [Hysteria2](/configuration/outbound/hysteria2/).
|
||||
|
||||
When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.
|
||||
|
||||
### 1.10.7
|
||||
#### 1.10.7
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@ -765,7 +777,7 @@ and the old outbound will be removed in sing-box 1.13.0.
|
||||
See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/)
|
||||
and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint).
|
||||
|
||||
### 1.10.2
|
||||
#### 1.10.2
|
||||
|
||||
* Add deprecated warnings
|
||||
* Fix proxying websocket connections in HTTP/mixed inbounds
|
||||
@ -902,7 +914,7 @@ See [Rule Action](/configuration/route/rule_action/).
|
||||
* Update quic-go to v0.48.0
|
||||
* Fixes and improvements
|
||||
|
||||
### 1.10.1
|
||||
#### 1.10.1
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
|
@ -6,7 +6,8 @@ icon: material/new-box
|
||||
|
||||
:material-plus: [interface_address](#interface_address)
|
||||
:material-plus: [network_interface_address](#network_interface_address)
|
||||
:material-plus: [default_interface_address](#default_interface_address)
|
||||
:material-plus: [default_interface_address](#default_interface_address)
|
||||
:material-plus: [preferred_by](#preferred_by)
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
@ -153,6 +154,10 @@ icon: material/new-box
|
||||
"wifi_bssid": [
|
||||
"00:00:00:00:00:00"
|
||||
],
|
||||
"preferred_by": [
|
||||
"tailscale",
|
||||
"wireguard"
|
||||
],
|
||||
"rule_set": [
|
||||
"geoip-cn",
|
||||
"geosite-cn"
|
||||
@ -428,6 +433,17 @@ Match WiFi SSID.
|
||||
|
||||
Match WiFi BSSID.
|
||||
|
||||
#### preferred_by
|
||||
|
||||
!!! question "Since sing-box 1.13.0"
|
||||
|
||||
Match specified outbounds' preferred routes.
|
||||
|
||||
| Type | Match |
|
||||
|-------------|-----------------------------------------------|
|
||||
| `tailscale` | Match MagicDNS domains and peers' allowed IPs |
|
||||
| `wireguard` | Match peers's allowed IPs |
|
||||
|
||||
#### rule_set
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
|
@ -6,7 +6,8 @@ icon: material/new-box
|
||||
|
||||
:material-plus: [interface_address](#interface_address)
|
||||
:material-plus: [network_interface_address](#network_interface_address)
|
||||
:material-plus: [default_interface_address](#default_interface_address)
|
||||
:material-plus: [default_interface_address](#default_interface_address)
|
||||
:material-plus: [preferred_by](#preferred_by)
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
@ -150,6 +151,10 @@ icon: material/new-box
|
||||
"wifi_bssid": [
|
||||
"00:00:00:00:00:00"
|
||||
],
|
||||
"preferred_by": [
|
||||
"tailscale",
|
||||
"wireguard"
|
||||
],
|
||||
"rule_set": [
|
||||
"geoip-cn",
|
||||
"geosite-cn"
|
||||
@ -425,6 +430,17 @@ icon: material/new-box
|
||||
|
||||
匹配 WiFi BSSID。
|
||||
|
||||
#### preferred_by
|
||||
|
||||
!!! question "自 sing-box 1.13.0 起"
|
||||
|
||||
匹配制定出站的首选路由。
|
||||
|
||||
| 类型 | 匹配 |
|
||||
|-------------|--------------------------------|
|
||||
| `tailscale` | 匹配 MagicDNS 域名和对端的 allowed IPs |
|
||||
| `wireguard` | 匹配对端的 allowed IPs |
|
||||
|
||||
#### rule_set
|
||||
|
||||
!!! question "自 sing-box 1.8.0 起"
|
||||
|
2
go.mod
2
go.mod
@ -27,7 +27,7 @@ require (
|
||||
github.com/sagernet/gomobile v0.1.8
|
||||
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
|
||||
github.com/sagernet/quic-go v0.52.0-beta.1
|
||||
github.com/sagernet/sing v0.7.6-0.20250814022454-614cd851ca6d
|
||||
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f
|
||||
github.com/sagernet/sing-mux v0.3.3
|
||||
github.com/sagernet/sing-quic v0.5.0
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||
|
4
go.sum
4
go.sum
@ -167,8 +167,8 @@ github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/l
|
||||
github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs=
|
||||
github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
|
||||
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing v0.7.6-0.20250814022454-614cd851ca6d h1:fTGA/vTcqceUqSiXeFAO99Iiai/nWBPV+yP8tPpHPf8=
|
||||
github.com/sagernet/sing v0.7.6-0.20250814022454-614cd851ca6d/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f h1:HIBo8l+tsS3wLwuI1E56uRTQw46QytXSUpZTP3vwG/U=
|
||||
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw=
|
||||
github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
||||
github.com/sagernet/sing-quic v0.5.0 h1:jNLIyVk24lFPvu8A4x+ZNEnZdI+Tg1rp7eCJ6v0Csak=
|
||||
|
@ -103,6 +103,7 @@ type RawDefaultRule struct {
|
||||
InterfaceAddress *badjson.TypedMap[string, badoption.Listable[badoption.Prefixable]] `json:"interface_address,omitempty"`
|
||||
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[badoption.Prefixable]] `json:"network_interface_address,omitempty"`
|
||||
DefaultInterfaceAddress badoption.Listable[badoption.Prefixable] `json:"default_interface_address,omitempty"`
|
||||
PreferredBy badoption.Listable[string] `json:"preferred_by,omitempty"`
|
||||
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
||||
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
||||
Invert bool `json:"invert,omitempty"`
|
||||
|
@ -258,7 +258,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {
|
||||
}
|
||||
var v any
|
||||
switch r.Version {
|
||||
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3:
|
||||
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4:
|
||||
v = &r.Options
|
||||
case 0:
|
||||
return E.New("missing rule-set version")
|
||||
@ -275,7 +275,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {
|
||||
|
||||
func (r PlainRuleSetCompat) Upgrade() (PlainRuleSet, error) {
|
||||
switch r.Version {
|
||||
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3:
|
||||
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4:
|
||||
default:
|
||||
return PlainRuleSet{}, E.New("unknown rule-set version: " + F.ToString(r.Version))
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@ -47,8 +46,6 @@ type DNSTransport struct {
|
||||
acceptDefaultResolvers bool
|
||||
dnsRouter adapter.DNSRouter
|
||||
endpointManager adapter.EndpointManager
|
||||
cfg *wgcfg.Config
|
||||
dnsCfg *nDNS.Config
|
||||
endpoint *Endpoint
|
||||
routePrefixes []netip.Prefix
|
||||
routes map[string][]adapter.DNSTransport
|
||||
@ -83,10 +80,10 @@ func (t *DNSTransport) Start(stage adapter.StartStage) error {
|
||||
if !isTailscale {
|
||||
return E.New("endpoint is not Tailscale: ", t.endpointTag)
|
||||
}
|
||||
if ep.onReconfig != nil {
|
||||
if ep.onReconfigHook != nil {
|
||||
return E.New("only one Tailscale DNS server is allowed for single endpoint")
|
||||
}
|
||||
ep.onReconfig = t.onReconfig
|
||||
ep.onReconfigHook = t.onReconfig
|
||||
t.endpoint = ep
|
||||
return nil
|
||||
}
|
||||
@ -95,14 +92,6 @@ func (t *DNSTransport) Reset() {
|
||||
}
|
||||
|
||||
func (t *DNSTransport) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *nDNS.Config) {
|
||||
if cfg == nil || dnsCfg == nil {
|
||||
return
|
||||
}
|
||||
if (t.cfg != nil && reflect.DeepEqual(t.cfg, cfg)) && (t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg)) {
|
||||
return
|
||||
}
|
||||
t.cfg = cfg
|
||||
t.dnsCfg = dnsCfg
|
||||
err := t.updateDNSServers(routerCfg, dnsCfg)
|
||||
if err != nil {
|
||||
t.logger.Error(E.Cause(err, "update DNS servers"))
|
||||
|
@ -10,9 +10,9 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@ -31,6 +31,7 @@ import (
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@ -49,8 +50,14 @@ import (
|
||||
"github.com/sagernet/tailscale/version"
|
||||
"github.com/sagernet/tailscale/wgengine"
|
||||
"github.com/sagernet/tailscale/wgengine/filter"
|
||||
"github.com/sagernet/tailscale/wgengine/router"
|
||||
"github.com/sagernet/tailscale/wgengine/wgcfg"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
var _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil)
|
||||
|
||||
func init() {
|
||||
version.SetVersion("sing-box " + C.Version)
|
||||
}
|
||||
@ -70,7 +77,12 @@ type Endpoint struct {
|
||||
server *tsnet.Server
|
||||
stack *stack.Stack
|
||||
filter *atomic.Pointer[filter.Filter]
|
||||
onReconfig wgengine.ReconfigListener
|
||||
onReconfigHook wgengine.ReconfigListener
|
||||
|
||||
cfg *wgcfg.Config
|
||||
dnsCfg *tsDNS.Config
|
||||
routeDomains atomic.TypedValue[map[string]bool]
|
||||
routePrefixes atomic.Pointer[netipx.IPSet]
|
||||
|
||||
acceptRoutes bool
|
||||
exitNode string
|
||||
@ -216,9 +228,7 @@ func (t *Endpoint) Start(stage adapter.StartStage) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if t.onReconfig != nil {
|
||||
t.server.ExportLocalBackend().ExportEngine().(wgengine.ExportedUserspaceEngine).SetOnReconfigListener(t.onReconfig)
|
||||
}
|
||||
t.server.ExportLocalBackend().ExportEngine().(wgengine.ExportedUserspaceEngine).SetOnReconfigListener(t.onReconfig)
|
||||
|
||||
ipStack := t.server.ExportNetstack().ExportIPStack()
|
||||
gErr := ipStack.SetSpoofing(tun.DefaultNIC, true)
|
||||
@ -253,8 +263,7 @@ func (t *Endpoint) Start(stage adapter.StartStage) error {
|
||||
if err != nil {
|
||||
return E.Cause(err, "update prefs")
|
||||
}
|
||||
t.filter = localBackend.ExportFilter()
|
||||
|
||||
t.filter = atomic.PointerForm(localBackend.ExportFilter())
|
||||
go t.watchState()
|
||||
return nil
|
||||
}
|
||||
@ -473,10 +482,58 @@ func (t *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
||||
t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
func (t *Endpoint) PreferredDomain(domain string) bool {
|
||||
routeDomains := t.routeDomains.Load()
|
||||
if routeDomains == nil {
|
||||
return false
|
||||
}
|
||||
return routeDomains[strings.ToLower(domain)]
|
||||
}
|
||||
|
||||
func (t *Endpoint) PreferredAddress(address netip.Addr) bool {
|
||||
routePrefixes := t.routePrefixes.Load()
|
||||
if routePrefixes == nil {
|
||||
return false
|
||||
}
|
||||
return routePrefixes.Contains(address)
|
||||
}
|
||||
|
||||
func (t *Endpoint) Server() *tsnet.Server {
|
||||
return t.server
|
||||
}
|
||||
|
||||
func (t *Endpoint) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *tsDNS.Config) {
|
||||
if cfg == nil || dnsCfg == nil {
|
||||
return
|
||||
}
|
||||
if (t.cfg != nil && reflect.DeepEqual(t.cfg, cfg)) && (t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg)) {
|
||||
return
|
||||
}
|
||||
t.cfg = cfg
|
||||
t.dnsCfg = dnsCfg
|
||||
|
||||
routeDomains := make(map[string]bool)
|
||||
for fqdn := range dnsCfg.Routes {
|
||||
routeDomains[fqdn.WithoutTrailingDot()] = true
|
||||
}
|
||||
for _, fqdn := range dnsCfg.SearchDomains {
|
||||
routeDomains[fqdn.WithoutTrailingDot()] = true
|
||||
}
|
||||
t.routeDomains.Store(routeDomains)
|
||||
|
||||
var builder netipx.IPSetBuilder
|
||||
for _, peer := range cfg.Peers {
|
||||
for _, allowedIP := range peer.AllowedIPs {
|
||||
builder.AddPrefix(allowedIP)
|
||||
}
|
||||
}
|
||||
t.routePrefixes.Store(common.Must1(builder.IPSet()))
|
||||
|
||||
if t.onReconfigHook != nil {
|
||||
t.onReconfigHook(cfg, routerCfg, dnsCfg)
|
||||
}
|
||||
}
|
||||
|
||||
func addressFromAddr(destination netip.Addr) tcpip.Address {
|
||||
if destination.Is6() {
|
||||
return tcpip.AddrFrom16(destination.As16())
|
||||
|
@ -22,6 +22,8 @@ import (
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
var _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil)
|
||||
|
||||
func RegisterEndpoint(registry *endpoint.Registry) {
|
||||
endpoint.Register[option.WireGuardEndpointOptions](registry, C.TypeWireGuard, NewEndpoint)
|
||||
}
|
||||
@ -210,3 +212,11 @@ func (w *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
||||
}
|
||||
return w.endpoint.ListenPacket(ctx, destination)
|
||||
}
|
||||
|
||||
func (w *Endpoint) PreferredDomain(domain string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *Endpoint) PreferredAddress(address netip.Addr) bool {
|
||||
return w.endpoint.Lookup(address) != nil
|
||||
}
|
||||
|
@ -21,6 +21,8 @@ import (
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
var _ adapter.OutboundWithPreferredRoutes = (*Outbound)(nil)
|
||||
|
||||
func RegisterOutbound(registry *outbound.Registry) {
|
||||
outbound.Register[option.LegacyWireGuardOutboundOptions](registry, C.TypeWireGuard, NewOutbound)
|
||||
}
|
||||
@ -158,3 +160,11 @@ func (o *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
||||
}
|
||||
return o.endpoint.ListenPacket(ctx, destination)
|
||||
}
|
||||
|
||||
func (o *Outbound) PreferredDomain(domain string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *Outbound) PreferredAddress(address netip.Addr) bool {
|
||||
return o.endpoint.Lookup(address) != nil
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio
|
||||
if len(options.DomainRegex) > 0 {
|
||||
item, err := NewDomainRegexItem(options.DomainRegex)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "domain_regex")
|
||||
return nil, err
|
||||
}
|
||||
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
@ -261,6 +261,11 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.PreferredBy) > 0 {
|
||||
item := NewPreferredByItem(ctx, options.PreferredBy)
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.RuleSet) > 0 {
|
||||
var matchSource bool
|
||||
if options.RuleSetIPCIDRMatchSource {
|
||||
|
86
route/rule/rule_item_preferred_by.go
Normal file
86
route/rule/rule_item_preferred_by.go
Normal file
@ -0,0 +1,86 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
var _ RuleItem = (*PreferredByItem)(nil)
|
||||
|
||||
type PreferredByItem struct {
|
||||
ctx context.Context
|
||||
outboundTags []string
|
||||
outbounds []adapter.OutboundWithPreferredRoutes
|
||||
}
|
||||
|
||||
func NewPreferredByItem(ctx context.Context, outboundTags []string) *PreferredByItem {
|
||||
return &PreferredByItem{
|
||||
ctx: ctx,
|
||||
outboundTags: outboundTags,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *PreferredByItem) Start() error {
|
||||
outboundManager := service.FromContext[adapter.OutboundManager](r.ctx)
|
||||
for _, outboundTag := range r.outboundTags {
|
||||
rawOutbound, loaded := outboundManager.Outbound(outboundTag)
|
||||
if !loaded {
|
||||
return E.New("outbound not found: ", outboundTag)
|
||||
}
|
||||
outboundWithPreferredRoutes, withRoutes := rawOutbound.(adapter.OutboundWithPreferredRoutes)
|
||||
if !withRoutes {
|
||||
return E.New("outbound type does not support preferred routes: ", rawOutbound.Type())
|
||||
}
|
||||
r.outbounds = append(r.outbounds, outboundWithPreferredRoutes)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PreferredByItem) Match(metadata *adapter.InboundContext) bool {
|
||||
var domainHost string
|
||||
if metadata.Domain != "" {
|
||||
domainHost = metadata.Domain
|
||||
} else {
|
||||
domainHost = metadata.Destination.Fqdn
|
||||
}
|
||||
if domainHost != "" {
|
||||
for _, outbound := range r.outbounds {
|
||||
if outbound.PreferredDomain(domainHost) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
if metadata.Destination.IsIP() {
|
||||
for _, outbound := range r.outbounds {
|
||||
if outbound.PreferredAddress(metadata.Destination.Addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(metadata.DestinationAddresses) > 0 {
|
||||
for _, address := range metadata.DestinationAddresses {
|
||||
for _, outbound := range r.outbounds {
|
||||
if outbound.PreferredAddress(address) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *PreferredByItem) String() string {
|
||||
description := "preferred_by="
|
||||
pLen := len(r.outboundTags)
|
||||
if pLen == 1 {
|
||||
description += F.ToString(r.outboundTags[0])
|
||||
} else {
|
||||
description += "[" + strings.Join(F.MapToString(r.outboundTags), " ") + "]"
|
||||
}
|
||||
return description
|
||||
}
|
@ -8,7 +8,9 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@ -30,6 +32,7 @@ type Endpoint struct {
|
||||
allowedAddress []netip.Prefix
|
||||
tunDevice Device
|
||||
device *device.Device
|
||||
allowedIPs *device.AllowedIPs
|
||||
pause pause.Manager
|
||||
pauseCallback *list.Element[pause.Callback]
|
||||
}
|
||||
@ -191,6 +194,7 @@ func (e *Endpoint) Start(resolve bool) error {
|
||||
if e.pause != nil {
|
||||
e.pauseCallback = e.pause.RegisterCallback(e.onPauseUpdated)
|
||||
}
|
||||
e.allowedIPs = (*device.AllowedIPs)(unsafe.Pointer(reflect.Indirect(reflect.ValueOf(wgDevice)).FieldByName("allowedips").UnsafeAddr()))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -218,6 +222,10 @@ func (e *Endpoint) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Endpoint) Lookup(address netip.Addr) *device.Peer {
|
||||
return e.allowedIPs.Lookup(address.AsSlice())
|
||||
}
|
||||
|
||||
func (e *Endpoint) onPauseUpdated(event int) {
|
||||
switch event {
|
||||
case pause.EventDevicePaused, pause.EventNetworkPause:
|
||||
|
Loading…
x
Reference in New Issue
Block a user