mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
Compare commits
9 Commits
dev-next
...
v1.9.0-alp
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2ee01f84b4 | ||
![]() |
336671053e | ||
![]() |
38c056b0f2 | ||
![]() |
4b6f649020 | ||
![]() |
4b256a94ab | ||
![]() |
548ac086ab | ||
![]() |
1252cd9f27 | ||
![]() |
1a0c3d0e0e | ||
![]() |
a053e87448 |
@ -51,11 +51,13 @@ type InboundContext struct {
|
||||
|
||||
// rule cache
|
||||
|
||||
IPCIDRMatchSource bool
|
||||
SourceAddressMatch bool
|
||||
SourcePortMatch bool
|
||||
DestinationAddressMatch bool
|
||||
DestinationPortMatch bool
|
||||
IPCIDRMatchSource bool
|
||||
SourceAddressMatch bool
|
||||
SourcePortMatch bool
|
||||
DestinationAddressMatch bool
|
||||
DestinationPortMatch bool
|
||||
DidMatch bool
|
||||
IgnoreDestinationIPCIDRMatch bool
|
||||
}
|
||||
|
||||
func (c *InboundContext) ResetRuleCache() {
|
||||
|
@ -84,6 +84,8 @@ type DNSRule interface {
|
||||
Rule
|
||||
DisableCache() bool
|
||||
RewriteTTL() *uint32
|
||||
WithAddressLimit() bool
|
||||
MatchAddressLimit(metadata *InboundContext) bool
|
||||
}
|
||||
|
||||
type RuleSet interface {
|
||||
@ -97,6 +99,7 @@ type RuleSet interface {
|
||||
type RuleSetMetadata struct {
|
||||
ContainsProcessRule bool
|
||||
ContainsWIFIRule bool
|
||||
ContainsIPCIDRRule bool
|
||||
}
|
||||
|
||||
type RuleSetStartContext interface {
|
||||
|
@ -4,6 +4,8 @@ package badtls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
@ -18,20 +20,32 @@ import (
|
||||
var _ N.ReadWaiter = (*ReadWaitConn)(nil)
|
||||
|
||||
type ReadWaitConn struct {
|
||||
*tls.STDConn
|
||||
halfAccess *sync.Mutex
|
||||
rawInput *bytes.Buffer
|
||||
input *bytes.Reader
|
||||
hand *bytes.Buffer
|
||||
readWaitOptions N.ReadWaitOptions
|
||||
tls.Conn
|
||||
halfAccess *sync.Mutex
|
||||
rawInput *bytes.Buffer
|
||||
input *bytes.Reader
|
||||
hand *bytes.Buffer
|
||||
readWaitOptions N.ReadWaitOptions
|
||||
tlsReadRecord func() error
|
||||
tlsHandlePostHandshakeMessage func() error
|
||||
}
|
||||
|
||||
func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {
|
||||
stdConn, isSTDConn := conn.(*tls.STDConn)
|
||||
if !isSTDConn {
|
||||
var (
|
||||
loaded bool
|
||||
tlsReadRecord func() error
|
||||
tlsHandlePostHandshakeMessage func() error
|
||||
)
|
||||
for _, tlsCreator := range tlsRegistry {
|
||||
loaded, tlsReadRecord, tlsHandlePostHandshakeMessage = tlsCreator(conn)
|
||||
if loaded {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !loaded {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
rawConn := reflect.Indirect(reflect.ValueOf(stdConn))
|
||||
rawConn := reflect.Indirect(reflect.ValueOf(conn))
|
||||
rawHalfConn := rawConn.FieldByName("in")
|
||||
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
|
||||
return nil, E.New("badtls: invalid half conn")
|
||||
@ -57,11 +71,13 @@ func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {
|
||||
}
|
||||
hand := (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr()))
|
||||
return &ReadWaitConn{
|
||||
STDConn: stdConn,
|
||||
halfAccess: halfAccess,
|
||||
rawInput: rawInput,
|
||||
input: input,
|
||||
hand: hand,
|
||||
Conn: conn,
|
||||
halfAccess: halfAccess,
|
||||
rawInput: rawInput,
|
||||
input: input,
|
||||
hand: hand,
|
||||
tlsReadRecord: tlsReadRecord,
|
||||
tlsHandlePostHandshakeMessage: tlsHandlePostHandshakeMessage,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -71,19 +87,19 @@ func (c *ReadWaitConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy
|
||||
}
|
||||
|
||||
func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
|
||||
err = c.Handshake()
|
||||
err = c.HandshakeContext(context.Background())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.halfAccess.Lock()
|
||||
defer c.halfAccess.Unlock()
|
||||
for c.input.Len() == 0 {
|
||||
err = tlsReadRecord(c.STDConn)
|
||||
err = c.tlsReadRecord()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for c.hand.Len() > 0 {
|
||||
err = tlsHandlePostHandshakeMessage(c.STDConn)
|
||||
err = c.tlsHandlePostHandshakeMessage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -100,7 +116,7 @@ func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
|
||||
if n != 0 && c.input.Len() == 0 && c.rawInput.Len() > 0 &&
|
||||
// recordType(c.rawInput.Bytes()[0]) == recordTypeAlert {
|
||||
c.rawInput.Bytes()[0] == 21 {
|
||||
_ = tlsReadRecord(c.STDConn)
|
||||
_ = c.tlsReadRecord()
|
||||
// return n, err // will be io.EOF on closeNotify
|
||||
}
|
||||
|
||||
@ -109,11 +125,27 @@ func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
|
||||
}
|
||||
|
||||
func (c *ReadWaitConn) Upstream() any {
|
||||
return c.STDConn
|
||||
return c.Conn
|
||||
}
|
||||
|
||||
//go:linkname tlsReadRecord crypto/tls.(*Conn).readRecord
|
||||
func tlsReadRecord(c *tls.STDConn) error
|
||||
var tlsRegistry []func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error)
|
||||
|
||||
//go:linkname tlsHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage
|
||||
func tlsHandlePostHandshakeMessage(c *tls.STDConn) error
|
||||
func init() {
|
||||
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
|
||||
tlsConn, loaded := conn.(*tls.STDConn)
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
return true, func() error {
|
||||
return stdTLSReadRecord(tlsConn)
|
||||
}, func() error {
|
||||
return stdTLSHandlePostHandshakeMessage(tlsConn)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//go:linkname stdTLSReadRecord crypto/tls.(*Conn).readRecord
|
||||
func stdTLSReadRecord(c *tls.STDConn) error
|
||||
|
||||
//go:linkname stdTLSHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage
|
||||
func stdTLSHandlePostHandshakeMessage(c *tls.STDConn) error
|
||||
|
31
common/badtls/read_wait_ech.go
Normal file
31
common/badtls/read_wait_ech.go
Normal file
@ -0,0 +1,31 @@
|
||||
//go:build go1.21 && !without_badtls && with_ech
|
||||
|
||||
package badtls
|
||||
|
||||
import (
|
||||
"net"
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/sagernet/cloudflare-tls"
|
||||
"github.com/sagernet/sing/common"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
|
||||
tlsConn, loaded := common.Cast[*tls.Conn](conn)
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
return true, func() error {
|
||||
return echReadRecord(tlsConn)
|
||||
}, func() error {
|
||||
return echHandlePostHandshakeMessage(tlsConn)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//go:linkname echReadRecord github.com/sagernet/cloudflare-tls.(*Conn).readRecord
|
||||
func echReadRecord(c *tls.Conn) error
|
||||
|
||||
//go:linkname echHandlePostHandshakeMessage github.com/sagernet/cloudflare-tls.(*Conn).handlePostHandshakeMessage
|
||||
func echHandlePostHandshakeMessage(c *tls.Conn) error
|
31
common/badtls/read_wait_utls.go
Normal file
31
common/badtls/read_wait_utls.go
Normal file
@ -0,0 +1,31 @@
|
||||
//go:build go1.21 && !without_badtls && with_utls
|
||||
|
||||
package badtls
|
||||
|
||||
import (
|
||||
"net"
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/utls"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
|
||||
tlsConn, loaded := common.Cast[*tls.UConn](conn)
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
return true, func() error {
|
||||
return utlsReadRecord(tlsConn.Conn)
|
||||
}, func() error {
|
||||
return utlsHandlePostHandshakeMessage(tlsConn.Conn)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//go:linkname utlsReadRecord github.com/sagernet/utls.(*Conn).readRecord
|
||||
func utlsReadRecord(c *tls.Conn) error
|
||||
|
||||
//go:linkname utlsHandlePostHandshakeMessage github.com/sagernet/utls.(*Conn).handlePostHandshakeMessage
|
||||
func utlsHandlePostHandshakeMessage(c *tls.Conn) error
|
@ -223,7 +223,7 @@ func getExecPathFromPID(pid uint32) (string, error) {
|
||||
r1, _, err := syscall.SyscallN(
|
||||
procQueryFullProcessImageNameW.Addr(),
|
||||
uintptr(h),
|
||||
uintptr(1),
|
||||
uintptr(0),
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(unsafe.Pointer(&size)),
|
||||
)
|
||||
|
5
constant/quic.go
Normal file
5
constant/quic.go
Normal file
@ -0,0 +1,5 @@
|
||||
//go:build with_quic
|
||||
|
||||
package constant
|
||||
|
||||
const WithQUIC = true
|
5
constant/quic_stub.go
Normal file
5
constant/quic_stub.go
Normal file
@ -0,0 +1,5 @@
|
||||
//go:build !with_quic
|
||||
|
||||
package constant
|
||||
|
||||
const WithQUIC = false
|
@ -2,6 +2,28 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.9.0-alpha.1
|
||||
|
||||
* `domain_suffix` behavior update **1**
|
||||
* `process_path` format update on Windows **2**
|
||||
* Add address filter DNS rule items **3**
|
||||
|
||||
**1**:
|
||||
|
||||
See [Migration](/migration/#domain_suffix-behavior-update).
|
||||
|
||||
**2**:
|
||||
|
||||
See [Migration](/migration/#process_path-format-update-on-windows).
|
||||
|
||||
**3**:
|
||||
|
||||
The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS if using this method.
|
||||
|
||||
See [Address Filter Fields](/configuration/dns/rule#address-filter-fields).
|
||||
|
||||
[Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) updated.
|
||||
|
||||
#### 1.8.5
|
||||
|
||||
* Fixes and improvements
|
||||
|
@ -21,8 +21,8 @@
|
||||
|
||||
### Fields
|
||||
|
||||
| Key | Format |
|
||||
|----------|--------------------------------|
|
||||
| Key | Format |
|
||||
|----------|---------------------------------|
|
||||
| `server` | List of [DNS Server](./server/) |
|
||||
| `rules` | List of [DNS Rule](./rule/) |
|
||||
| `fakeip` | [FakeIP](./fakeip/) |
|
||||
|
@ -1,7 +1,13 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.9.0"
|
||||
|
||||
:material-plus: [geoip](#geoip)
|
||||
:material-plus: [ip_cidr](#ip_cidr)
|
||||
:material-plus: [ip_is_private](#ip_is_private)
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
|
||||
:material-plus: [rule_set](#rule_set)
|
||||
@ -53,11 +59,19 @@ icon: material/alert-decagram
|
||||
"source_geoip": [
|
||||
"private"
|
||||
],
|
||||
"geoip": [
|
||||
"cn"
|
||||
],
|
||||
"source_ip_cidr": [
|
||||
"10.0.0.0/24",
|
||||
"192.168.0.1"
|
||||
],
|
||||
"source_ip_is_private": false,
|
||||
"ip_cidr": [
|
||||
"10.0.0.0/24",
|
||||
"192.168.0.1"
|
||||
],
|
||||
"ip_is_private": false,
|
||||
"source_port": [
|
||||
12345
|
||||
],
|
||||
@ -312,6 +326,32 @@ Disable cache and save cache in this query.
|
||||
|
||||
Rewrite TTL in DNS responses.
|
||||
|
||||
### Address Filter Fields
|
||||
|
||||
Only takes effect for IP address requests. When the query results do not match the address filtering rule items, the current rule will be skipped.
|
||||
|
||||
!!! note ""
|
||||
|
||||
`ip_cidr` items in included rule sets also takes effect as an address filtering field.
|
||||
|
||||
#### geoip
|
||||
|
||||
!!! question "Since sing-box 1.9.0"
|
||||
|
||||
Match GeoIP with query response.
|
||||
|
||||
#### ip_cidr
|
||||
|
||||
!!! question "Since sing-box 1.9.0"
|
||||
|
||||
Match IP CIDR with query response.
|
||||
|
||||
#### ip_is_private
|
||||
|
||||
!!! question "Since sing-box 1.9.0"
|
||||
|
||||
Match private IP with query response.
|
||||
|
||||
### Logical Fields
|
||||
|
||||
#### type
|
||||
|
@ -1,7 +1,13 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.9.0 中的更改"
|
||||
|
||||
:material-plus: [geoip](#geoip)
|
||||
:material-plus: [ip_cidr](#ip_cidr)
|
||||
:material-plus: [ip_is_private](#ip_is_private)
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
||||
:material-plus: [rule_set](#rule_set)
|
||||
@ -53,10 +59,19 @@ icon: material/alert-decagram
|
||||
"source_geoip": [
|
||||
"private"
|
||||
],
|
||||
"geoip": [
|
||||
"cn"
|
||||
],
|
||||
"source_ip_cidr": [
|
||||
"10.0.0.0/24"
|
||||
"10.0.0.0/24",
|
||||
"192.168.0.1"
|
||||
],
|
||||
"source_ip_is_private": false,
|
||||
"ip_cidr": [
|
||||
"10.0.0.0/24",
|
||||
"192.168.0.1"
|
||||
],
|
||||
"ip_is_private": false,
|
||||
"source_port": [
|
||||
12345
|
||||
],
|
||||
@ -307,6 +322,32 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
重写 DNS 回应中的 TTL。
|
||||
|
||||
### 地址筛选字段
|
||||
|
||||
仅对IP地址请求生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。
|
||||
|
||||
!!! note ""
|
||||
|
||||
引用的规则集中的 `ip_cidr` 项也作为地址筛选字段生效。
|
||||
|
||||
#### geoip
|
||||
|
||||
!!! question "自 sing-box 1.8.0 起"
|
||||
|
||||
与查询响应匹配 GeoIP。
|
||||
|
||||
#### ip_cidr
|
||||
|
||||
!!! question "自 sing-box 1.8.0 起"
|
||||
|
||||
与查询相应匹配 IP CIDR。
|
||||
|
||||
#### ip_is_private
|
||||
|
||||
!!! question "自 sing-box 1.8.0 起"
|
||||
|
||||
与查询响应匹配非公开 IP。
|
||||
|
||||
### 逻辑字段
|
||||
|
||||
#### type
|
||||
|
@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
|
||||
### Structure
|
||||
|
@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "自 sing-box 1.8.0 起"
|
||||
|
||||
### 结构
|
||||
|
@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
|
||||
:material-delete-alert: [store_mode](#store_mode)
|
||||
|
@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
||||
:material-delete-alert: [store_mode](#store_mode)
|
||||
|
@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
# Experimental
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
|
@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
# 实验性
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
|
||||
:material-plus: [gso](#gso)
|
||||
|
@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
||||
:material-plus: [gso](#gso)
|
||||
|
@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
|
||||
:material-plus: [gso](#gso)
|
||||
|
@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
||||
:material-plus: [gso](#gso)
|
||||
|
@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
# Route
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
|
@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
# 路由
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
|
||||
:material-plus: [rule_set](#rule_set)
|
||||
|
@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
||||
:material-plus: [rule_set](#rule_set)
|
||||
|
@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
### Structure
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
|
@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
# Rule Set
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
|
@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
# Source Format
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
|
@ -1,8 +1,3 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
|
||||
:material-alert-decagram: [utls](#utls)
|
||||
|
@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
||||
:material-alert-decagram: [utls](#utls)
|
||||
|
@ -290,52 +290,6 @@ flowchart TB
|
||||
|
||||
=== ":material-dns: DNS rules"
|
||||
|
||||
!!! info
|
||||
|
||||
DNS rules are optional if FakeIP is used.
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"tag": "google",
|
||||
"address": "tls://8.8.8.8"
|
||||
},
|
||||
{
|
||||
"tag": "local",
|
||||
"address": "223.5.5.5",
|
||||
"detour": "direct"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"outbound": "any",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Direct",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Global",
|
||||
"server": "google"
|
||||
},
|
||||
{
|
||||
"geosite": "geolocation-cn",
|
||||
"server": "local"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-dns: DNS rules (1.8.0+)"
|
||||
|
||||
!!! info
|
||||
|
||||
DNS rules are optional if FakeIP is used.
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@ -382,74 +336,78 @@ flowchart TB
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-router-network: Route rules"
|
||||
=== ":material-dns: DNS rules (1.9.0+)"
|
||||
|
||||
!!! warning "DNS leaks"
|
||||
|
||||
The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS if using this method.
|
||||
|
||||
```json
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"tag": "block"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns"
|
||||
},
|
||||
{
|
||||
"port": 53
|
||||
}
|
||||
],
|
||||
"outbound": "dns"
|
||||
"tag": "google",
|
||||
"address": "tls://8.8.8.8"
|
||||
},
|
||||
{
|
||||
"geoip": "private",
|
||||
"outbound": "direct"
|
||||
"tag": "local",
|
||||
"address": "https://223.5.5.5/dns-query",
|
||||
"detour": "direct"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"outbound": "any",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Direct",
|
||||
"outbound": "direct"
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Global",
|
||||
"outbound": "default"
|
||||
"server": "google"
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"rules": [
|
||||
{
|
||||
"port": 853
|
||||
},
|
||||
{
|
||||
"network": "udp",
|
||||
"port": 443
|
||||
},
|
||||
{
|
||||
"protocol": "stun"
|
||||
}
|
||||
],
|
||||
"outbound": "block"
|
||||
"rule_set": "geosite-geolocation-cn",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"geosite": "geolocation-cn",
|
||||
"outbound": "direct"
|
||||
"clash_mode": "Default",
|
||||
"server": "google"
|
||||
},
|
||||
{
|
||||
"rule_set": "geoip-cn",
|
||||
"server": "local"
|
||||
}
|
||||
]
|
||||
},
|
||||
"route": {
|
||||
"rule_set": [
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-geolocation-cn",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geoip-cn",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs"
|
||||
}
|
||||
]
|
||||
},
|
||||
"experimental": {
|
||||
"clash_api": {
|
||||
"default_mode": "Leak"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-router-network: Route rules (1.8.0+)"
|
||||
=== ":material-router-network: Route rules"
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -2,6 +2,28 @@
|
||||
icon: material/arrange-bring-forward
|
||||
---
|
||||
|
||||
## 1.9.0
|
||||
|
||||
!!! warning "Unstable"
|
||||
|
||||
This version is still under development, and the following migration guide may be changed in the future.
|
||||
|
||||
### `domain_suffix` behavior update
|
||||
|
||||
For historical reasons, sing-box's `domain_suffix` rule matches literal prefixes instead of the same as other projects.
|
||||
|
||||
sing-box 1.9.0 modifies the behavior of `domain_suffix`: If the rule value is prefixed with `.`,
|
||||
the behavior is unchanged, otherwise it matches `(domain|.+\.domain)` instead.
|
||||
|
||||
### `process_path` format update on Windows
|
||||
|
||||
The `process_path` rule of sing-box is inherited from Clash,
|
||||
the original code uses the local system's path format (e.g. `\Device\HarddiskVolume1\folder\program.exe`),
|
||||
but when the device has multiple disks, the HarddiskVolume serial number is not stable.
|
||||
|
||||
sing-box 1.9.0 make QueryFullProcessImageNameW output a Win32 path (such as `C:\folder\program.exe`),
|
||||
which will disrupt the existing `process_path` use cases in Windows.
|
||||
|
||||
## 1.8.0
|
||||
|
||||
### :material-close-box: Migrate cache file from Clash API to independent options
|
||||
|
@ -2,6 +2,27 @@
|
||||
icon: material/arrange-bring-forward
|
||||
---
|
||||
|
||||
## 1.9.0
|
||||
|
||||
!!! warning "不稳定的"
|
||||
|
||||
该版本仍在开发中,迁移指南可能将在未来更改。
|
||||
|
||||
### `domain_suffix` 行为更新
|
||||
|
||||
由于历史原因,sing-box 的 `domain_suffix` 规则匹配字面前缀,而不与其他项目相同。
|
||||
|
||||
sing-box 1.9.0 修改了 `domain_suffix` 的行为:如果规则值以 `.` 为前缀则行为不变,否则改为匹配 `(domain|.+\.domain)`。
|
||||
|
||||
### 对 Windows 上 `process_path` 格式的更新
|
||||
|
||||
sing-box 的 `process_path` 规则继承自Clash,
|
||||
原始代码使用本地系统的路径格式(例如 `\Device\HarddiskVolume1\folder\program.exe`),
|
||||
但是当设备有多个硬盘时,该 HarddiskVolume 系列号并不稳定。
|
||||
|
||||
sing-box 1.9.0 使 QueryFullProcessImageNameW 输出 Win32 路径(如 `C:\folder\program.exe`),
|
||||
这将会破坏现有的 Windows `process_path` 用例。
|
||||
|
||||
## 1.8.0
|
||||
|
||||
### :material-close-box: 将缓存文件从 Clash API 迁移到独立选项
|
||||
|
@ -3,6 +3,7 @@ package experimental
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
@ -27,11 +28,26 @@ func NewClashServer(ctx context.Context, router adapter.Router, logFactory log.O
|
||||
}
|
||||
|
||||
func CalculateClashModeList(options option.Options) []string {
|
||||
var clashMode []string
|
||||
clashMode = append(clashMode, extraClashModeFromRule(common.PtrValueOrDefault(options.Route).Rules)...)
|
||||
clashMode = append(clashMode, extraClashModeFromDNSRule(common.PtrValueOrDefault(options.DNS).Rules)...)
|
||||
clashMode = common.FilterNotDefault(common.Uniq(clashMode))
|
||||
return clashMode
|
||||
var clashModes []string
|
||||
clashModes = append(clashModes, extraClashModeFromRule(common.PtrValueOrDefault(options.Route).Rules)...)
|
||||
clashModes = append(clashModes, extraClashModeFromDNSRule(common.PtrValueOrDefault(options.DNS).Rules)...)
|
||||
clashModes = common.FilterNotDefault(common.Uniq(clashModes))
|
||||
predefinedOrder := []string{
|
||||
"Rule", "Global", "Direct",
|
||||
}
|
||||
var newClashModes []string
|
||||
for _, mode := range clashModes {
|
||||
if !common.Contains(predefinedOrder, mode) {
|
||||
newClashModes = append(newClashModes, mode)
|
||||
}
|
||||
}
|
||||
sort.Strings(newClashModes)
|
||||
for _, mode := range predefinedOrder {
|
||||
if common.Contains(clashModes, mode) {
|
||||
newClashModes = append(newClashModes, mode)
|
||||
}
|
||||
}
|
||||
return newClashModes
|
||||
}
|
||||
|
||||
func extraClashModeFromRule(rules []option.Rule) []string {
|
||||
|
@ -32,6 +32,8 @@ type BoxService struct {
|
||||
instance *box.Box
|
||||
pauseManager pause.Manager
|
||||
urlTestHistoryStorage *urltest.HistoryStorage
|
||||
|
||||
servicePauseFields
|
||||
}
|
||||
|
||||
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
|
||||
@ -75,16 +77,6 @@ func (s *BoxService) Close() error {
|
||||
return s.instance.Close()
|
||||
}
|
||||
|
||||
func (s *BoxService) Sleep() {
|
||||
s.pauseManager.DevicePause()
|
||||
_ = s.instance.Router().ResetNetwork()
|
||||
}
|
||||
|
||||
func (s *BoxService) Wake() {
|
||||
s.pauseManager.DeviceWake()
|
||||
_ = s.instance.Router().ResetNetwork()
|
||||
}
|
||||
|
||||
var (
|
||||
_ platform.Interface = (*platformInterfaceWrapper)(nil)
|
||||
_ log.PlatformWriter = (*platformInterfaceWrapper)(nil)
|
||||
|
41
experimental/libbox/service_pause.go
Normal file
41
experimental/libbox/service_pause.go
Normal file
@ -0,0 +1,41 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type servicePauseFields struct {
|
||||
pauseAccess sync.Mutex
|
||||
pauseTimer *time.Timer
|
||||
}
|
||||
|
||||
func (s *BoxService) Pause() {
|
||||
s.pauseAccess.Lock()
|
||||
defer s.pauseAccess.Unlock()
|
||||
|
||||
if s.pauseTimer != nil {
|
||||
s.pauseTimer.Stop()
|
||||
}
|
||||
|
||||
s.pauseTimer = time.AfterFunc(time.Minute, s.pause)
|
||||
}
|
||||
|
||||
func (s *BoxService) pause() {
|
||||
s.pauseManager.DevicePause()
|
||||
_ = s.instance.Router().ResetNetwork()
|
||||
}
|
||||
|
||||
func (s *BoxService) Wake() {
|
||||
s.pauseAccess.Lock()
|
||||
defer s.pauseAccess.Unlock()
|
||||
|
||||
if s.pauseTimer != nil {
|
||||
s.pauseTimer.Stop()
|
||||
s.pauseTimer = nil
|
||||
return
|
||||
}
|
||||
|
||||
s.pauseManager.DeviceWake()
|
||||
_ = s.instance.Router().ResetNetwork()
|
||||
}
|
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/common/humanize"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
_ "github.com/sagernet/sing-box/include"
|
||||
)
|
||||
|
||||
var (
|
||||
|
16
go.mod
16
go.mod
@ -12,7 +12,7 @@ require (
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/gofrs/uuid/v5 v5.0.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240204152450-ca2dc33955c1
|
||||
github.com/libdns/alidns v1.0.3
|
||||
github.com/libdns/cloudflare v0.1.0
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
@ -26,8 +26,8 @@ require (
|
||||
github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e
|
||||
github.com/sagernet/quic-go v0.40.1
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||
github.com/sagernet/sing v0.3.0
|
||||
github.com/sagernet/sing-dns v0.1.12
|
||||
github.com/sagernet/sing v0.3.1-0.20240105061852-782bc05c5573
|
||||
github.com/sagernet/sing-dns v0.1.13-0.20240203102504-27e217be9060
|
||||
github.com/sagernet/sing-mux v0.2.0
|
||||
github.com/sagernet/sing-quic v0.1.8
|
||||
github.com/sagernet/sing-shadowsocks v0.2.6
|
||||
@ -44,11 +44,11 @@ require (
|
||||
github.com/stretchr/testify v1.8.4
|
||||
go.uber.org/zap v1.26.0
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.18.0
|
||||
golang.org/x/net v0.20.0
|
||||
golang.org/x/sys v0.16.0
|
||||
golang.org/x/crypto v0.19.0
|
||||
golang.org/x/net v0.21.0
|
||||
golang.org/x/sys v0.17.0
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||
google.golang.org/grpc v1.60.1
|
||||
google.golang.org/grpc v1.61.0
|
||||
google.golang.org/protobuf v1.32.0
|
||||
howett.net/plist v1.0.1
|
||||
)
|
||||
@ -91,7 +91,7 @@ require (
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.17.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/blake3 v1.2.1 // indirect
|
||||
|
36
go.sum
36
go.sum
@ -42,15 +42,15 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240204152450-ca2dc33955c1 h1:L3pm9Kf2G6gJVYawz2SrI5QnV1wzHYbqmKnSHHXJAb8=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240204152450-ca2dc33955c1/go.mod h1:izxuNQZeFrbx2nK2fAyN5iNUB34Fe9j0nK4PwLzAkKw=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
@ -109,10 +109,10 @@ github.com/sagernet/quic-go v0.40.1/go.mod h1:CcKTpzTAISxrM4PA5M20/wYuz9Tj6Tx4Dw
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
||||
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
||||
github.com/sagernet/sing v0.3.0 h1:PIDVFZHnQAAYRL1UYqNM+0k5s8f/tb1lUW6UDcQiOc8=
|
||||
github.com/sagernet/sing v0.3.0/go.mod h1:9pfuAH6mZfgnz/YjP6xu5sxx882rfyjpcrTdUpd6w3g=
|
||||
github.com/sagernet/sing-dns v0.1.12 h1:1HqZ+ln+Rezx/aJMStaS0d7oPeX2EobSV1NT537kyj4=
|
||||
github.com/sagernet/sing-dns v0.1.12/go.mod h1:rx/DTOisneQpCgNQ4jbFU/JNEtnz0lYcHXenlVzpjEU=
|
||||
github.com/sagernet/sing v0.3.1-0.20240105061852-782bc05c5573 h1:1wGN3eNanp8r+Y3bNBys3ZnAVF5gdtDoDwtosMZEbgA=
|
||||
github.com/sagernet/sing v0.3.1-0.20240105061852-782bc05c5573/go.mod h1:9pfuAH6mZfgnz/YjP6xu5sxx882rfyjpcrTdUpd6w3g=
|
||||
github.com/sagernet/sing-dns v0.1.13-0.20240203102504-27e217be9060 h1:ah78H3NjlBEov2MGAKC5Wtn71LhFfRatVrJ88PCQPjE=
|
||||
github.com/sagernet/sing-dns v0.1.13-0.20240203102504-27e217be9060/go.mod h1:IxOqfSb6Zt6UVCy8fJpDxb2XxqzHUytNqeOuJfaiLu8=
|
||||
github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo=
|
||||
github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
|
||||
github.com/sagernet/sing-quic v0.1.8 h1:G4iBXAKIII+uTzd55oZ/9cAQswGjlvHh/0yKMQioDS0=
|
||||
@ -168,16 +168,16 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
|
||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -188,10 +188,10 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
||||
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
@ -204,10 +204,10 @@ golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY=
|
||||
google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
|
||||
google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA=
|
||||
google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0=
|
||||
google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
|
@ -15,7 +15,6 @@ import (
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
"github.com/sagernet/sing-box/common/uot"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/include"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
@ -109,8 +108,8 @@ func (n *Naive) Start() error {
|
||||
|
||||
if common.Contains(n.network, N.NetworkUDP) {
|
||||
err := n.configureHTTP3Listener()
|
||||
if !include.WithQUIC && len(n.network) > 1 {
|
||||
log.Warn(E.Cause(err, "naive http3 disabled"))
|
||||
if !C.WithQUIC && len(n.network) > 1 {
|
||||
n.logger.Warn(E.Cause(err, "naive http3 disabled"))
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1,10 +0,0 @@
|
||||
//go:build with_quic
|
||||
|
||||
package include
|
||||
|
||||
import (
|
||||
_ "github.com/sagernet/sing-box/transport/v2rayquic"
|
||||
_ "github.com/sagernet/sing-dns/quic"
|
||||
)
|
||||
|
||||
const WithQUIC = true
|
@ -16,8 +16,6 @@ import (
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
const WithQUIC = false
|
||||
|
||||
func init() {
|
||||
dns.RegisterTransport([]string{"quic", "h3"}, func(name string, ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) {
|
||||
return nil, C.ErrQUICNotIncluded
|
||||
|
21
include/tz_android.go
Normal file
21
include/tz_android.go
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// kanged from https://github.com/golang/mobile/blob/c713f31d574bb632a93f169b2cc99c9e753fef0e/app/android.go#L89
|
||||
|
||||
package include
|
||||
|
||||
// #include <time.h>
|
||||
import "C"
|
||||
import "time"
|
||||
|
||||
func init() {
|
||||
var currentT C.time_t
|
||||
var currentTM C.struct_tm
|
||||
C.time(¤tT)
|
||||
C.localtime_r(¤tT, ¤tTM)
|
||||
tzOffset := int(currentTM.tm_gmtoff)
|
||||
tz := C.GoString(currentTM.tm_zone)
|
||||
time.Local = time.FixedZone(tz, tzOffset)
|
||||
}
|
30
include/tz_ios.go
Normal file
30
include/tz_ios.go
Normal file
@ -0,0 +1,30 @@
|
||||
package include
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation
|
||||
#import <Foundation/Foundation.h>
|
||||
const char* getSystemTimeZone() {
|
||||
NSTimeZone *timeZone = [NSTimeZone systemTimeZone];
|
||||
NSString *timeZoneName = [timeZone description];
|
||||
return [timeZoneName UTF8String];
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tzDescription := C.GoString(C.getSystemTimeZone())
|
||||
if len(tzDescription) == 0 {
|
||||
return
|
||||
}
|
||||
location, err := time.LoadLocation(strings.Split(tzDescription, " ")[0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
time.Local = location
|
||||
}
|
@ -77,6 +77,9 @@ type DefaultDNSRule struct {
|
||||
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"`
|
||||
IPCIDR Listable[string] `json:"ip_cidr,omitempty"`
|
||||
IPIsPrivate bool `json:"ip_is_private,omitempty"`
|
||||
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
|
||||
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
||||
|
@ -2,13 +2,13 @@ package route
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-dns"
|
||||
"github.com/sagernet/sing/common/cache"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@ -37,41 +37,51 @@ func (m *DNSReverseMapping) Query(address netip.Addr) (string, bool) {
|
||||
return domain, loaded
|
||||
}
|
||||
|
||||
func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool) (context.Context, dns.Transport, dns.DomainStrategy) {
|
||||
func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, index int) (context.Context, dns.Transport, dns.DomainStrategy, adapter.DNSRule, int) {
|
||||
metadata := adapter.ContextFrom(ctx)
|
||||
if metadata == nil {
|
||||
panic("no context")
|
||||
}
|
||||
for i, rule := range r.dnsRules {
|
||||
metadata.ResetRuleCache()
|
||||
if rule.Match(metadata) {
|
||||
detour := rule.Outbound()
|
||||
transport, loaded := r.transportMap[detour]
|
||||
if !loaded {
|
||||
r.dnsLogger.ErrorContext(ctx, "transport not found: ", detour)
|
||||
continue
|
||||
}
|
||||
if _, isFakeIP := transport.(adapter.FakeIPTransport); isFakeIP && !allowFakeIP {
|
||||
continue
|
||||
}
|
||||
r.dnsLogger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour)
|
||||
if rule.DisableCache() {
|
||||
ctx = dns.ContextWithDisableCache(ctx, true)
|
||||
}
|
||||
if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil {
|
||||
ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL)
|
||||
}
|
||||
if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded {
|
||||
return ctx, transport, domainStrategy
|
||||
} else {
|
||||
return ctx, transport, r.defaultDomainStrategy
|
||||
if index < len(r.dnsRules) {
|
||||
dnsRules := r.dnsRules
|
||||
if index != -1 {
|
||||
dnsRules = dnsRules[index+1:]
|
||||
}
|
||||
for ruleIndex, rule := range dnsRules {
|
||||
metadata.ResetRuleCache()
|
||||
if rule.Match(metadata) {
|
||||
detour := rule.Outbound()
|
||||
transport, loaded := r.transportMap[detour]
|
||||
if !loaded {
|
||||
r.dnsLogger.ErrorContext(ctx, "transport not found: ", detour)
|
||||
continue
|
||||
}
|
||||
if _, isFakeIP := transport.(adapter.FakeIPTransport); isFakeIP && !allowFakeIP {
|
||||
continue
|
||||
}
|
||||
displayRuleIndex := ruleIndex
|
||||
if index != -1 {
|
||||
displayRuleIndex += index + 1
|
||||
}
|
||||
r.dnsLogger.DebugContext(ctx, "match[", displayRuleIndex, "] ", rule.String(), " => ", detour)
|
||||
if rule.DisableCache() {
|
||||
ctx = dns.ContextWithDisableCache(ctx, true)
|
||||
}
|
||||
if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil {
|
||||
ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL)
|
||||
}
|
||||
if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded {
|
||||
return ctx, transport, domainStrategy, rule, ruleIndex
|
||||
} else {
|
||||
return ctx, transport, r.defaultDomainStrategy, rule, ruleIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if domainStrategy, dsLoaded := r.transportDomainStrategy[r.defaultTransport]; dsLoaded {
|
||||
return ctx, r.defaultTransport, domainStrategy
|
||||
return ctx, r.defaultTransport, domainStrategy, nil, -1
|
||||
} else {
|
||||
return ctx, r.defaultTransport, r.defaultDomainStrategy
|
||||
return ctx, r.defaultTransport, r.defaultDomainStrategy, nil, -1
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +96,8 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er
|
||||
)
|
||||
response, cached = r.dnsClient.ExchangeCache(ctx, message)
|
||||
if !cached {
|
||||
ctx, metadata := adapter.AppendContext(ctx)
|
||||
var metadata *adapter.InboundContext
|
||||
ctx, metadata = adapter.AppendContext(ctx)
|
||||
if len(message.Question) > 0 {
|
||||
metadata.QueryType = message.Question[0].Qtype
|
||||
switch metadata.QueryType {
|
||||
@ -97,17 +108,47 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er
|
||||
}
|
||||
metadata.Domain = fqdnToDomain(message.Question[0].Name)
|
||||
}
|
||||
ctx, transport, strategy := r.matchDNS(ctx, true)
|
||||
ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout)
|
||||
defer cancel()
|
||||
response, err = r.dnsClient.Exchange(ctx, transport, message, strategy)
|
||||
if err != nil && len(message.Question) > 0 {
|
||||
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", formatQuestion(message.Question[0].String())))
|
||||
var (
|
||||
transport dns.Transport
|
||||
strategy dns.DomainStrategy
|
||||
rule adapter.DNSRule
|
||||
ruleIndex int
|
||||
)
|
||||
ruleIndex = -1
|
||||
for {
|
||||
var (
|
||||
dnsCtx context.Context
|
||||
cancel context.CancelFunc
|
||||
addressLimit bool
|
||||
)
|
||||
|
||||
dnsCtx, transport, strategy, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex)
|
||||
dnsCtx, cancel = context.WithTimeout(dnsCtx, C.DNSTimeout)
|
||||
if rule != nil && rule.WithAddressLimit() && isAddressQuery(message) {
|
||||
addressLimit = true
|
||||
response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, strategy, func(response *mDNS.Msg) bool {
|
||||
metadata.DestinationAddresses, _ = dns.MessageToAddresses(response)
|
||||
return rule.MatchAddressLimit(metadata)
|
||||
})
|
||||
} else {
|
||||
addressLimit = false
|
||||
response, err = r.dnsClient.Exchange(dnsCtx, transport, message, strategy)
|
||||
}
|
||||
cancel()
|
||||
if err != nil {
|
||||
if errors.Is(err, dns.ErrResponseRejected) {
|
||||
r.dnsLogger.DebugContext(ctx, E.Cause(err, "response rejected for ", formatQuestion(message.Question[0].String())))
|
||||
} else if len(message.Question) > 0 {
|
||||
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", formatQuestion(message.Question[0].String())))
|
||||
} else {
|
||||
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))
|
||||
}
|
||||
}
|
||||
if !addressLimit || err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(message.Question) > 0 && response != nil {
|
||||
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) {
|
||||
@ -125,22 +166,57 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS
|
||||
r.dnsLogger.DebugContext(ctx, "lookup domain ", domain)
|
||||
ctx, metadata := adapter.AppendContext(ctx)
|
||||
metadata.Domain = domain
|
||||
ctx, transport, transportStrategy := r.matchDNS(ctx, false)
|
||||
if strategy == dns.DomainStrategyAsIS {
|
||||
strategy = transportStrategy
|
||||
var (
|
||||
transport dns.Transport
|
||||
transportStrategy dns.DomainStrategy
|
||||
rule adapter.DNSRule
|
||||
ruleIndex int
|
||||
resultAddrs []netip.Addr
|
||||
err error
|
||||
)
|
||||
ruleIndex = -1
|
||||
for {
|
||||
var (
|
||||
dnsCtx context.Context
|
||||
cancel context.CancelFunc
|
||||
addressLimit bool
|
||||
)
|
||||
metadata.ResetRuleCache()
|
||||
metadata.DestinationAddresses = nil
|
||||
dnsCtx, transport, transportStrategy, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex)
|
||||
if strategy == dns.DomainStrategyAsIS {
|
||||
strategy = transportStrategy
|
||||
}
|
||||
dnsCtx, cancel = context.WithTimeout(dnsCtx, C.DNSTimeout)
|
||||
if rule != nil && rule.WithAddressLimit() {
|
||||
addressLimit = true
|
||||
resultAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, strategy, func(responseAddrs []netip.Addr) bool {
|
||||
metadata.DestinationAddresses = responseAddrs
|
||||
return rule.MatchAddressLimit(metadata)
|
||||
})
|
||||
} else {
|
||||
addressLimit = false
|
||||
resultAddrs, err = r.dnsClient.Lookup(dnsCtx, transport, domain, strategy)
|
||||
}
|
||||
cancel()
|
||||
if err != nil {
|
||||
if errors.Is(err, dns.ErrResponseRejected) {
|
||||
r.dnsLogger.DebugContext(ctx, "response rejected for ", domain)
|
||||
} else {
|
||||
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
|
||||
}
|
||||
} else if len(resultAddrs) == 0 {
|
||||
r.dnsLogger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result")
|
||||
err = dns.RCodeNameError
|
||||
}
|
||||
if !addressLimit || err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout)
|
||||
defer cancel()
|
||||
addrs, err := r.dnsClient.Lookup(ctx, transport, domain, strategy)
|
||||
if len(addrs) > 0 {
|
||||
r.dnsLogger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(addrs), " "))
|
||||
} else if err != nil {
|
||||
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
|
||||
} else {
|
||||
r.dnsLogger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result")
|
||||
err = dns.RCodeNameError
|
||||
if len(resultAddrs) > 0 {
|
||||
r.dnsLogger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(resultAddrs), " "))
|
||||
}
|
||||
return addrs, err
|
||||
return resultAddrs, err
|
||||
}
|
||||
|
||||
func (r *Router) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) {
|
||||
@ -154,10 +230,13 @@ func (r *Router) ClearDNSCache() {
|
||||
}
|
||||
}
|
||||
|
||||
func LogDNSAnswers(logger log.ContextLogger, ctx context.Context, domain string, answers []mDNS.RR) {
|
||||
for _, answer := range answers {
|
||||
logger.InfoContext(ctx, "exchanged ", domain, " ", mDNS.Type(answer.Header().Rrtype).String(), " ", formatQuestion(answer.String()))
|
||||
func isAddressQuery(message *mDNS.Msg) bool {
|
||||
for _, question := range message.Question {
|
||||
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func fqdnToDomain(fqdn string) string {
|
||||
|
@ -97,3 +97,7 @@ func isWIFIDNSRule(rule option.DefaultDNSRule) bool {
|
||||
func isWIFIHeadlessRule(rule option.DefaultHeadlessRule) bool {
|
||||
return len(rule.WIFISSID) > 0 || len(rule.WIFIBSSID) > 0
|
||||
}
|
||||
|
||||
func isIPCIDRHeadlessRule(rule option.DefaultHeadlessRule) bool {
|
||||
return len(rule.IPCIDR) > 0 || rule.IPSet != nil
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ type abstractDefaultRule struct {
|
||||
sourceAddressItems []RuleItem
|
||||
sourcePortItems []RuleItem
|
||||
destinationAddressItems []RuleItem
|
||||
destinationIPCIDRItems []RuleItem
|
||||
destinationPortItems []RuleItem
|
||||
allItems []RuleItem
|
||||
ruleSetItem RuleItem
|
||||
@ -64,6 +65,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||
}
|
||||
|
||||
if len(r.sourceAddressItems) > 0 && !metadata.SourceAddressMatch {
|
||||
metadata.DidMatch = true
|
||||
for _, item := range r.sourceAddressItems {
|
||||
if item.Match(metadata) {
|
||||
metadata.SourceAddressMatch = true
|
||||
@ -73,6 +75,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||
}
|
||||
|
||||
if len(r.sourcePortItems) > 0 && !metadata.SourcePortMatch {
|
||||
metadata.DidMatch = true
|
||||
for _, item := range r.sourcePortItems {
|
||||
if item.Match(metadata) {
|
||||
metadata.SourcePortMatch = true
|
||||
@ -82,6 +85,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||
}
|
||||
|
||||
if len(r.destinationAddressItems) > 0 && !metadata.DestinationAddressMatch {
|
||||
metadata.DidMatch = true
|
||||
for _, item := range r.destinationAddressItems {
|
||||
if item.Match(metadata) {
|
||||
metadata.DestinationAddressMatch = true
|
||||
@ -90,7 +94,18 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||
}
|
||||
}
|
||||
|
||||
if !metadata.IgnoreDestinationIPCIDRMatch && len(r.destinationIPCIDRItems) > 0 && !metadata.DestinationAddressMatch {
|
||||
metadata.DidMatch = true
|
||||
for _, item := range r.destinationIPCIDRItems {
|
||||
if item.Match(metadata) {
|
||||
metadata.DestinationAddressMatch = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.destinationPortItems) > 0 && !metadata.DestinationPortMatch {
|
||||
metadata.DidMatch = true
|
||||
for _, item := range r.destinationPortItems {
|
||||
if item.Match(metadata) {
|
||||
metadata.DestinationPortMatch = true
|
||||
@ -100,6 +115,9 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||
}
|
||||
|
||||
for _, item := range r.items {
|
||||
if _, isRuleSet := item.(*RuleSetItem); !isRuleSet {
|
||||
metadata.DidMatch = true
|
||||
}
|
||||
if !item.Match(metadata) {
|
||||
return r.invert
|
||||
}
|
||||
@ -113,7 +131,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||
return r.invert
|
||||
}
|
||||
|
||||
if len(r.destinationAddressItems) > 0 && !metadata.DestinationAddressMatch {
|
||||
if ((!metadata.IgnoreDestinationIPCIDRMatch && len(r.destinationIPCIDRItems) > 0) || len(r.destinationAddressItems) > 0) && !metadata.DestinationAddressMatch {
|
||||
return r.invert
|
||||
}
|
||||
|
||||
@ -121,6 +139,10 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||
return r.invert
|
||||
}
|
||||
|
||||
if !metadata.DidMatch {
|
||||
return false
|
||||
}
|
||||
|
||||
return !r.invert
|
||||
}
|
||||
|
||||
|
@ -109,7 +109,7 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt
|
||||
}
|
||||
if len(options.GeoIP) > 0 {
|
||||
item := NewGeoIPItem(router, logger, false, options.GeoIP)
|
||||
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.SourceIPCIDR) > 0 {
|
||||
@ -130,12 +130,12 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "ipcidr")
|
||||
}
|
||||
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if options.IPIsPrivate {
|
||||
item := NewIPIsPrivateItem(false)
|
||||
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.SourcePort) > 0 {
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
@ -111,6 +112,11 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
|
||||
rule.sourceAddressItems = append(rule.sourceAddressItems, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.GeoIP) > 0 {
|
||||
item := NewGeoIPItem(router, logger, false, options.GeoIP)
|
||||
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.SourceIPCIDR) > 0 {
|
||||
item, err := NewIPCIDRItem(true, options.SourceIPCIDR)
|
||||
if err != nil {
|
||||
@ -119,11 +125,24 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
|
||||
rule.sourceAddressItems = append(rule.sourceAddressItems, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.IPCIDR) > 0 {
|
||||
item, err := NewIPCIDRItem(false, options.IPCIDR)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "ip_cidr")
|
||||
}
|
||||
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if options.SourceIPIsPrivate {
|
||||
item := NewIPIsPrivateItem(true)
|
||||
rule.sourceAddressItems = append(rule.sourceAddressItems, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if options.IPIsPrivate {
|
||||
item := NewIPIsPrivateItem(false)
|
||||
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.SourcePort) > 0 {
|
||||
item := NewPortItem(true, options.SourcePort)
|
||||
rule.sourcePortItems = append(rule.sourcePortItems, item)
|
||||
@ -211,6 +230,34 @@ func (r *DefaultDNSRule) RewriteTTL() *uint32 {
|
||||
return r.rewriteTTL
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) WithAddressLimit() bool {
|
||||
if len(r.destinationIPCIDRItems) > 0 {
|
||||
return true
|
||||
}
|
||||
for _, rawRule := range r.items {
|
||||
ruleSet, isRuleSet := rawRule.(*RuleSetItem)
|
||||
if !isRuleSet {
|
||||
continue
|
||||
}
|
||||
if ruleSet.ContainsIPCIDRRule() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) Match(metadata *adapter.InboundContext) bool {
|
||||
metadata.IgnoreDestinationIPCIDRMatch = true
|
||||
defer func() {
|
||||
metadata.IgnoreDestinationIPCIDRMatch = false
|
||||
}()
|
||||
return r.abstractDefaultRule.Match(metadata)
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) MatchAddressLimit(metadata *adapter.InboundContext) bool {
|
||||
return r.abstractDefaultRule.Match(metadata)
|
||||
}
|
||||
|
||||
var _ adapter.DNSRule = (*LogicalDNSRule)(nil)
|
||||
|
||||
type LogicalDNSRule struct {
|
||||
@ -254,3 +301,47 @@ func (r *LogicalDNSRule) DisableCache() bool {
|
||||
func (r *LogicalDNSRule) RewriteTTL() *uint32 {
|
||||
return r.rewriteTTL
|
||||
}
|
||||
|
||||
func (r *LogicalDNSRule) WithAddressLimit() bool {
|
||||
for _, rawRule := range r.rules {
|
||||
switch rule := rawRule.(type) {
|
||||
case *DefaultDNSRule:
|
||||
if rule.WithAddressLimit() {
|
||||
return true
|
||||
}
|
||||
case *LogicalDNSRule:
|
||||
if rule.WithAddressLimit() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *LogicalDNSRule) Match(metadata *adapter.InboundContext) bool {
|
||||
if r.mode == C.LogicalTypeAnd {
|
||||
return common.All(r.rules, func(it adapter.HeadlessRule) bool {
|
||||
metadata.ResetRuleCache()
|
||||
return it.(adapter.DNSRule).Match(metadata)
|
||||
}) != r.invert
|
||||
} else {
|
||||
return common.Any(r.rules, func(it adapter.HeadlessRule) bool {
|
||||
metadata.ResetRuleCache()
|
||||
return it.(adapter.DNSRule).Match(metadata)
|
||||
}) != r.invert
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LogicalDNSRule) MatchAddressLimit(metadata *adapter.InboundContext) bool {
|
||||
if r.mode == C.LogicalTypeAnd {
|
||||
return common.All(r.rules, func(it adapter.HeadlessRule) bool {
|
||||
metadata.ResetRuleCache()
|
||||
return it.(adapter.DNSRule).MatchAddressLimit(metadata)
|
||||
}) != r.invert
|
||||
} else {
|
||||
return common.Any(r.rules, func(it adapter.HeadlessRule) bool {
|
||||
metadata.ResetRuleCache()
|
||||
return it.(adapter.DNSRule).MatchAddressLimit(metadata)
|
||||
}) != r.invert
|
||||
}
|
||||
}
|
||||
|
@ -80,11 +80,11 @@ func NewDefaultHeadlessRule(router adapter.Router, options option.DefaultHeadles
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "ipcidr")
|
||||
}
|
||||
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
} else if options.IPSet != nil {
|
||||
item := NewRawIPCIDRItem(false, options.IPSet)
|
||||
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.SourcePort) > 0 {
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
)
|
||||
@ -13,7 +14,7 @@ var _ RuleItem = (*RuleSetItem)(nil)
|
||||
type RuleSetItem struct {
|
||||
router adapter.Router
|
||||
tagList []string
|
||||
setList []adapter.HeadlessRule
|
||||
setList []adapter.RuleSet
|
||||
ipcidrMatchSource bool
|
||||
}
|
||||
|
||||
@ -46,6 +47,12 @@ func (r *RuleSetItem) Match(metadata *adapter.InboundContext) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *RuleSetItem) ContainsIPCIDRRule() bool {
|
||||
return common.Any(r.setList, func(ruleSet adapter.RuleSet) bool {
|
||||
return ruleSet.Metadata().ContainsIPCIDRRule
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuleSetItem) String() string {
|
||||
if len(r.tagList) == 1 {
|
||||
return F.ToString("rule_set=", r.tagList[0])
|
||||
|
@ -55,6 +55,7 @@ func NewLocalRuleSet(router adapter.Router, options option.RuleSet) (*LocalRuleS
|
||||
var metadata adapter.RuleSetMetadata
|
||||
metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
|
||||
metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
||||
metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
|
||||
return &LocalRuleSet{rules, metadata}, nil
|
||||
}
|
||||
|
||||
|
@ -150,6 +150,7 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error {
|
||||
}
|
||||
s.metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
|
||||
s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
||||
s.metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
|
||||
s.rules = rules
|
||||
return nil
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user