Compare commits
15 Commits
v1.14.0-al
...
draft-wind
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1b6bf07ae | ||
|
|
fd09582c6a | ||
|
|
6c55bbd921 | ||
|
|
2e15cf82b2 | ||
|
|
6a7fe70ee8 | ||
|
|
a6e4184252 | ||
|
|
83b19121da | ||
|
|
ddf24c2154 | ||
|
|
ede12fa117 | ||
|
|
e98b4ad449 | ||
|
|
d09182614c | ||
|
|
6381de7bab | ||
|
|
b0c6762bc1 | ||
|
|
7425100bac | ||
|
|
d454aa0fdf |
33
.github/detect_track.sh
vendored
Executable file
33
.github/detect_track.sh
vendored
Executable file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
branches=$(git branch -r --contains HEAD)
|
||||||
|
if echo "$branches" | grep -q 'origin/stable'; then
|
||||||
|
track=stable
|
||||||
|
elif echo "$branches" | grep -q 'origin/testing'; then
|
||||||
|
track=testing
|
||||||
|
elif echo "$branches" | grep -q 'origin/oldstable'; then
|
||||||
|
track=oldstable
|
||||||
|
else
|
||||||
|
echo "ERROR: HEAD is not on any known release branch (stable/testing/oldstable)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$track" == "stable" ]]; then
|
||||||
|
tag=$(git describe --tags --exact-match HEAD 2>/dev/null || true)
|
||||||
|
if [[ -n "$tag" && "$tag" == *"-"* ]]; then
|
||||||
|
track=beta
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$track" in
|
||||||
|
stable) name=sing-box; docker_tag=latest ;;
|
||||||
|
beta) name=sing-box-beta; docker_tag=latest-beta ;;
|
||||||
|
testing) name=sing-box-testing; docker_tag=latest-testing ;;
|
||||||
|
oldstable) name=sing-box-oldstable; docker_tag=latest-oldstable ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "track=${track} name=${name} docker_tag=${docker_tag}" >&2
|
||||||
|
echo "TRACK=${track}" >> "$GITHUB_ENV"
|
||||||
|
echo "NAME=${name}" >> "$GITHUB_ENV"
|
||||||
|
echo "DOCKER_TAG=${docker_tag}" >> "$GITHUB_ENV"
|
||||||
19
.github/workflows/docker.yml
vendored
19
.github/workflows/docker.yml
vendored
@@ -19,7 +19,6 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
build_binary:
|
build_binary:
|
||||||
name: Build binary
|
name: Build binary
|
||||||
if: github.event_name != 'release' || github.event.release.target_commitish != 'oldstable'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
@@ -260,13 +259,13 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "ref=$ref"
|
echo "ref=$ref"
|
||||||
echo "ref=$ref" >> $GITHUB_OUTPUT
|
echo "ref=$ref" >> $GITHUB_OUTPUT
|
||||||
if [[ $ref == *"-"* ]]; then
|
- name: Checkout
|
||||||
latest=latest-beta
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
else
|
with:
|
||||||
latest=latest
|
ref: ${{ steps.ref.outputs.ref }}
|
||||||
fi
|
fetch-depth: 0
|
||||||
echo "latest=$latest"
|
- name: Detect track
|
||||||
echo "latest=$latest" >> $GITHUB_OUTPUT
|
run: bash .github/detect_track.sh
|
||||||
- name: Download digests
|
- name: Download digests
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
@@ -286,11 +285,11 @@ jobs:
|
|||||||
working-directory: /tmp/digests
|
working-directory: /tmp/digests
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create \
|
docker buildx imagetools create \
|
||||||
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}" \
|
-t "${{ env.REGISTRY_IMAGE }}:${{ env.DOCKER_TAG }}" \
|
||||||
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \
|
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \
|
||||||
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
|
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
|
||||||
- name: Inspect image
|
- name: Inspect image
|
||||||
if: github.event_name != 'push'
|
if: github.event_name != 'push'
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}
|
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ env.DOCKER_TAG }}
|
||||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}
|
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}
|
||||||
|
|||||||
16
.github/workflows/linux.yml
vendored
16
.github/workflows/linux.yml
vendored
@@ -11,11 +11,6 @@ on:
|
|||||||
description: "Version name"
|
description: "Version name"
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
forceBeta:
|
|
||||||
description: "Force beta"
|
|
||||||
required: false
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
@@ -23,7 +18,6 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
calculate_version:
|
calculate_version:
|
||||||
name: Calculate version
|
name: Calculate version
|
||||||
if: github.event_name != 'release' || github.event.release.target_commitish != 'oldstable'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
version: ${{ steps.outputs.outputs.version }}
|
version: ${{ steps.outputs.outputs.version }}
|
||||||
@@ -168,14 +162,8 @@ jobs:
|
|||||||
- name: Set mtime
|
- name: Set mtime
|
||||||
run: |-
|
run: |-
|
||||||
TZ=UTC touch -t '197001010000' dist/sing-box
|
TZ=UTC touch -t '197001010000' dist/sing-box
|
||||||
- name: Set name
|
- name: Detect track
|
||||||
if: (! contains(needs.calculate_version.outputs.version, '-')) && !inputs.forceBeta
|
run: bash .github/detect_track.sh
|
||||||
run: |-
|
|
||||||
echo "NAME=sing-box" >> "$GITHUB_ENV"
|
|
||||||
- name: Set beta name
|
|
||||||
if: contains(needs.calculate_version.outputs.version, '-') || inputs.forceBeta
|
|
||||||
run: |-
|
|
||||||
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
|
|
||||||
- name: Set version
|
- name: Set version
|
||||||
run: |-
|
run: |-
|
||||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||||
|
|||||||
Submodule clients/android updated: 82ffed6cd7...834a5f7df0
Submodule clients/apple updated: bcdd385b79...6b790c7a80
@@ -2,6 +2,14 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
|
#### 1.14.0-alpha.7
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.13.4
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.14.0-alpha.4
|
#### 1.14.0-alpha.4
|
||||||
|
|
||||||
* Refactor ACME support to certificate provider system **1**
|
* Refactor ACME support to certificate provider system **1**
|
||||||
@@ -21,10 +29,6 @@ See [Cloudflare Origin CA](/configuration/shared/certificate-provider/cloudflare
|
|||||||
|
|
||||||
See [Tailscale](/configuration/shared/certificate-provider/tailscale).
|
See [Tailscale](/configuration/shared/certificate-provider/tailscale).
|
||||||
|
|
||||||
#### 1.13.4-beta.1
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.13.3
|
#### 1.13.3
|
||||||
|
|
||||||
* Add OpenWrt and Alpine APK packages to release **1**
|
* Add OpenWrt and Alpine APK packages to release **1**
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ icon: material/alert-decagram
|
|||||||
(`source_port` || `source_port_range`) &&
|
(`source_port` || `source_port_range`) &&
|
||||||
`other fields`
|
`other fields`
|
||||||
|
|
||||||
Additionally, included rule-sets can be considered merged rather than as a single rule sub-item.
|
Additionally, each branch inside an included rule-set can be considered merged into the outer rule, while different branches keep OR semantics.
|
||||||
|
|
||||||
#### inbound
|
#### inbound
|
||||||
|
|
||||||
@@ -577,4 +577,4 @@ Match any IP with query response.
|
|||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
Included rules.
|
Included rules.
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ icon: material/alert-decagram
|
|||||||
(`source_port` || `source_port_range`) &&
|
(`source_port` || `source_port_range`) &&
|
||||||
`other fields`
|
`other fields`
|
||||||
|
|
||||||
另外,引用的规则集可视为被合并,而不是作为一个单独的规则子项。
|
另外,引用规则集中的每个分支都可视为与外层规则合并,不同分支之间仍保持 OR 语义。
|
||||||
|
|
||||||
#### inbound
|
#### inbound
|
||||||
|
|
||||||
@@ -581,4 +581,4 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
包括的规则。
|
包括的规则。
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ icon: material/new-box
|
|||||||
(`source_port` || `source_port_range`) &&
|
(`source_port` || `source_port_range`) &&
|
||||||
`other fields`
|
`other fields`
|
||||||
|
|
||||||
Additionally, included rule-sets can be considered merged rather than as a single rule sub-item.
|
Additionally, each branch inside an included rule-set can be considered merged into the outer rule, while different branches keep OR semantics.
|
||||||
|
|
||||||
#### inbound
|
#### inbound
|
||||||
|
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ icon: material/new-box
|
|||||||
(`source_port` || `source_port_range`) &&
|
(`source_port` || `source_port_range`) &&
|
||||||
`other fields`
|
`other fields`
|
||||||
|
|
||||||
另外,引用的规则集可视为被合并,而不是作为一个单独的规则子项。
|
另外,引用规则集中的每个分支都可视为与外层规则合并,不同分支之间仍保持 OR 语义。
|
||||||
|
|
||||||
#### inbound
|
#### inbound
|
||||||
|
|
||||||
@@ -532,4 +532,4 @@ icon: material/new-box
|
|||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
包括的规则。
|
包括的规则。
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ type HTTPRequest interface {
|
|||||||
type HTTPResponse interface {
|
type HTTPResponse interface {
|
||||||
GetContent() (*StringBox, error)
|
GetContent() (*StringBox, error)
|
||||||
WriteTo(path string) error
|
WriteTo(path string) error
|
||||||
|
WriteToWithProgress(path string, handler HTTPResponseWriteToProgressHandler) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPResponseWriteToProgressHandler interface {
|
||||||
|
Update(progress int64, total int64)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -239,3 +244,31 @@ func (h *httpResponse) WriteTo(path string) error {
|
|||||||
defer file.Close()
|
defer file.Close()
|
||||||
return common.Error(bufio.Copy(file, h.Body))
|
return common.Error(bufio.Copy(file, h.Body))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *httpResponse) WriteToWithProgress(path string, handler HTTPResponseWriteToProgressHandler) error {
|
||||||
|
defer h.Body.Close()
|
||||||
|
file, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
return common.Error(bufio.Copy(&progressWriter{
|
||||||
|
writer: file,
|
||||||
|
handler: handler,
|
||||||
|
total: h.ContentLength,
|
||||||
|
}, h.Body))
|
||||||
|
}
|
||||||
|
|
||||||
|
type progressWriter struct {
|
||||||
|
writer io.Writer
|
||||||
|
handler HTTPResponseWriteToProgressHandler
|
||||||
|
total int64
|
||||||
|
written int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *progressWriter) Write(p []byte) (int, error) {
|
||||||
|
n, err := w.writer.Write(p)
|
||||||
|
w.written += int64(n)
|
||||||
|
w.handler.Update(w.written, w.total)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|||||||
6
go.mod
6
go.mod
@@ -6,6 +6,7 @@ require (
|
|||||||
github.com/anthropics/anthropic-sdk-go v1.26.0
|
github.com/anthropics/anthropic-sdk-go v1.26.0
|
||||||
github.com/anytls/sing-anytls v0.0.11
|
github.com/anytls/sing-anytls v0.0.11
|
||||||
github.com/caddyserver/certmagic v0.25.2
|
github.com/caddyserver/certmagic v0.25.2
|
||||||
|
github.com/caddyserver/zerossl v0.1.5
|
||||||
github.com/coder/websocket v1.8.14
|
github.com/coder/websocket v1.8.14
|
||||||
github.com/cretz/bine v0.2.0
|
github.com/cretz/bine v0.2.0
|
||||||
github.com/database64128/tfo-go/v2 v2.3.2
|
github.com/database64128/tfo-go/v2 v2.3.2
|
||||||
@@ -19,6 +20,7 @@ require (
|
|||||||
github.com/libdns/acmedns v0.5.0
|
github.com/libdns/acmedns v0.5.0
|
||||||
github.com/libdns/alidns v1.0.6
|
github.com/libdns/alidns v1.0.6
|
||||||
github.com/libdns/cloudflare v0.2.2
|
github.com/libdns/cloudflare v0.2.2
|
||||||
|
github.com/libdns/libdns v1.1.1
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
github.com/mdlayher/netlink v1.9.0
|
github.com/mdlayher/netlink v1.9.0
|
||||||
github.com/metacubex/utls v1.8.4
|
github.com/metacubex/utls v1.8.4
|
||||||
@@ -41,7 +43,7 @@ require (
|
|||||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
||||||
github.com/sagernet/sing-tun v0.8.7-0.20260323120017-8eb4e8acfc2d
|
github.com/sagernet/sing-tun v0.8.7-0.20260327130747-85dc2d52a0b8
|
||||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
|
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
|
||||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1
|
github.com/sagernet/smux v1.5.50-sing-box-mod.1
|
||||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7
|
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7
|
||||||
@@ -69,7 +71,6 @@ require (
|
|||||||
github.com/akutz/memconn v0.1.0 // indirect
|
github.com/akutz/memconn v0.1.0 // indirect
|
||||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
|
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
|
||||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||||
github.com/caddyserver/zerossl v0.1.5 // indirect
|
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
|
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
|
||||||
github.com/database64128/netx-go v0.1.1 // indirect
|
github.com/database64128/netx-go v0.1.1 // indirect
|
||||||
@@ -96,7 +97,6 @@ require (
|
|||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/libdns/libdns v1.1.1 // indirect
|
|
||||||
github.com/mdlayher/socket v0.5.1 // indirect
|
github.com/mdlayher/socket v0.5.1 // indirect
|
||||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -248,8 +248,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq
|
|||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
||||||
github.com/sagernet/sing-tun v0.8.7-0.20260323120017-8eb4e8acfc2d h1:vi0j6301f6H8t2GYgAC2PA2AdnGdMwkP34B4+N03Qt4=
|
github.com/sagernet/sing-tun v0.8.7-0.20260327130747-85dc2d52a0b8 h1:YxCs60xDya7R4NT+89v4Il+LjsSfCT/ceHegpe0xuls=
|
||||||
github.com/sagernet/sing-tun v0.8.7-0.20260323120017-8eb4e8acfc2d/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs=
|
github.com/sagernet/sing-tun v0.8.7-0.20260327130747-85dc2d52a0b8/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs=
|
||||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
|
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
|
||||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=
|
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=
|
||||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=
|
github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=
|
||||||
|
|||||||
@@ -614,7 +614,7 @@ func (t *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
|||||||
return packetConn, nil
|
return packetConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
func (t *Endpoint) PrepareConnection(ctx context.Context, network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||||
tsFilter := t.filter.Load()
|
tsFilter := t.filter.Load()
|
||||||
if tsFilter != nil {
|
if tsFilter != nil {
|
||||||
var ipProto ipproto.Proto
|
var ipProto ipproto.Proto
|
||||||
|
|||||||
@@ -245,6 +245,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||||||
inbound.autoRedirect, err = tun.NewAutoRedirect(tun.AutoRedirectOptions{
|
inbound.autoRedirect, err = tun.NewAutoRedirect(tun.AutoRedirectOptions{
|
||||||
TunOptions: &inbound.tunOptions,
|
TunOptions: &inbound.tunOptions,
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
|
ConnContext: log.ContextWithNewID,
|
||||||
Handler: (*autoRedirectHandler)(inbound),
|
Handler: (*autoRedirectHandler)(inbound),
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
NetworkMonitor: networkManager.NetworkMonitor(),
|
NetworkMonitor: networkManager.NetworkMonitor(),
|
||||||
@@ -257,7 +258,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize auto-redirect")
|
return nil, E.Cause(err, "initialize auto-redirect")
|
||||||
}
|
}
|
||||||
if !C.IsAndroid {
|
if C.IsLinux {
|
||||||
inbound.tunOptions.AutoRedirectMarkMode = true
|
inbound.tunOptions.AutoRedirectMarkMode = true
|
||||||
err = networkManager.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark)
|
err = networkManager.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -453,7 +454,7 @@ func (t *Inbound) Close() error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
func (t *Inbound) PrepareConnection(ctx context.Context, network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||||
var ipVersion uint8
|
var ipVersion uint8
|
||||||
if !destination.IsIPv6() {
|
if !destination.IsIPv6() {
|
||||||
ipVersion = 4
|
ipVersion = 4
|
||||||
@@ -511,21 +512,35 @@ func (t *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
|||||||
|
|
||||||
type autoRedirectHandler Inbound
|
type autoRedirectHandler Inbound
|
||||||
|
|
||||||
func (t *autoRedirectHandler) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
func autoRedirectProcessInfoFromContext(ctx context.Context) *adapter.ConnectionOwner {
|
||||||
|
metadata := tun.AutoRedirectMetadataFromContext(ctx)
|
||||||
|
if metadata == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &adapter.ConnectionOwner{
|
||||||
|
ProcessID: metadata.ProcessID,
|
||||||
|
ProcessPath: metadata.ProcessPath,
|
||||||
|
UserId: metadata.UserId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *autoRedirectHandler) PrepareConnection(ctx context.Context, network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||||
var ipVersion uint8
|
var ipVersion uint8
|
||||||
if !destination.IsIPv6() {
|
if !destination.IsIPv6() {
|
||||||
ipVersion = 4
|
ipVersion = 4
|
||||||
} else {
|
} else {
|
||||||
ipVersion = 6
|
ipVersion = 6
|
||||||
}
|
}
|
||||||
routeDestination, err := t.router.PreMatch(adapter.InboundContext{
|
metadata := adapter.InboundContext{
|
||||||
Inbound: t.tag,
|
Inbound: t.tag,
|
||||||
InboundType: C.TypeTun,
|
InboundType: C.TypeTun,
|
||||||
IPVersion: ipVersion,
|
IPVersion: ipVersion,
|
||||||
Network: network,
|
Network: network,
|
||||||
Source: source,
|
Source: source,
|
||||||
Destination: destination,
|
Destination: destination,
|
||||||
}, routeContext, timeout, true)
|
ProcessInfo: autoRedirectProcessInfoFromContext(ctx),
|
||||||
|
}
|
||||||
|
routeDestination, err := t.router.PreMatch(metadata, routeContext, timeout, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch {
|
switch {
|
||||||
case rule.IsBypassed(err):
|
case rule.IsBypassed(err):
|
||||||
@@ -542,12 +557,12 @@ func (t *autoRedirectHandler) PrepareConnection(network string, source M.Socksad
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *autoRedirectHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (t *autoRedirectHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
ctx = log.ContextWithNewID(ctx)
|
|
||||||
var metadata adapter.InboundContext
|
var metadata adapter.InboundContext
|
||||||
metadata.Inbound = t.tag
|
metadata.Inbound = t.tag
|
||||||
metadata.InboundType = C.TypeTun
|
metadata.InboundType = C.TypeTun
|
||||||
metadata.Source = source
|
metadata.Source = source
|
||||||
metadata.Destination = destination
|
metadata.Destination = destination
|
||||||
|
metadata.ProcessInfo = autoRedirectProcessInfoFromContext(ctx)
|
||||||
|
|
||||||
t.logger.InfoContext(ctx, "inbound redirect connection from ", metadata.Source)
|
t.logger.InfoContext(ctx, "inbound redirect connection from ", metadata.Source)
|
||||||
t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ func (w *Endpoint) Close() error {
|
|||||||
return w.endpoint.Close()
|
return w.endpoint.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
func (w *Endpoint) PrepareConnection(ctx context.Context, network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||||
var ipVersion uint8
|
var ipVersion uint8
|
||||||
if !destination.IsIPv6() {
|
if !destination.IsIPv6() {
|
||||||
ipVersion = 4
|
ipVersion = 4
|
||||||
|
|||||||
@@ -393,6 +393,9 @@ func (r *NetworkManager) AutoRedirectOutputMark() uint32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *NetworkManager) AutoRedirectOutputMarkFunc() control.Func {
|
func (r *NetworkManager) AutoRedirectOutputMarkFunc() control.Func {
|
||||||
|
if !C.IsLinux || C.IsAndroid {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return func(network, address string, conn syscall.RawConn) error {
|
return func(network, address string, conn syscall.RawConn) error {
|
||||||
if r.autoRedirectOutputMark == 0 {
|
if r.autoRedirectOutputMark == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
126
route/rule/match_state.go
Normal file
126
route/rule/match_state.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package rule
|
||||||
|
|
||||||
|
import "github.com/sagernet/sing-box/adapter"
|
||||||
|
|
||||||
|
type ruleMatchState uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
ruleMatchSourceAddress ruleMatchState = 1 << iota
|
||||||
|
ruleMatchSourcePort
|
||||||
|
ruleMatchDestinationAddress
|
||||||
|
ruleMatchDestinationPort
|
||||||
|
)
|
||||||
|
|
||||||
|
type ruleMatchStateSet uint16
|
||||||
|
|
||||||
|
func singleRuleMatchState(state ruleMatchState) ruleMatchStateSet {
|
||||||
|
return 1 << state
|
||||||
|
}
|
||||||
|
|
||||||
|
func emptyRuleMatchState() ruleMatchStateSet {
|
||||||
|
return singleRuleMatchState(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s ruleMatchStateSet) isEmpty() bool {
|
||||||
|
return s == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s ruleMatchStateSet) contains(state ruleMatchState) bool {
|
||||||
|
return s&(1<<state) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s ruleMatchStateSet) add(state ruleMatchState) ruleMatchStateSet {
|
||||||
|
return s | singleRuleMatchState(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s ruleMatchStateSet) merge(other ruleMatchStateSet) ruleMatchStateSet {
|
||||||
|
return s | other
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s ruleMatchStateSet) combine(other ruleMatchStateSet) ruleMatchStateSet {
|
||||||
|
if s.isEmpty() || other.isEmpty() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var combined ruleMatchStateSet
|
||||||
|
for left := ruleMatchState(0); left < 16; left++ {
|
||||||
|
if !s.contains(left) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for right := ruleMatchState(0); right < 16; right++ {
|
||||||
|
if !other.contains(right) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
combined = combined.add(left | right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return combined
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s ruleMatchStateSet) withBase(base ruleMatchState) ruleMatchStateSet {
|
||||||
|
if s.isEmpty() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var withBase ruleMatchStateSet
|
||||||
|
for state := ruleMatchState(0); state < 16; state++ {
|
||||||
|
if !s.contains(state) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
withBase = withBase.add(state | base)
|
||||||
|
}
|
||||||
|
return withBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s ruleMatchStateSet) filter(allowed func(ruleMatchState) bool) ruleMatchStateSet {
|
||||||
|
var filtered ruleMatchStateSet
|
||||||
|
for state := ruleMatchState(0); state < 16; state++ {
|
||||||
|
if !s.contains(state) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if allowed(state) {
|
||||||
|
filtered = filtered.add(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
|
type ruleStateMatcher interface {
|
||||||
|
matchStates(metadata *adapter.InboundContext) ruleMatchStateSet
|
||||||
|
}
|
||||||
|
|
||||||
|
type ruleStateMatcherWithBase interface {
|
||||||
|
matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchHeadlessRuleStates(rule adapter.HeadlessRule, metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||||
|
return matchHeadlessRuleStatesWithBase(rule, metadata, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchHeadlessRuleStatesWithBase(rule adapter.HeadlessRule, metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet {
|
||||||
|
if matcher, isStateMatcher := rule.(ruleStateMatcherWithBase); isStateMatcher {
|
||||||
|
return matcher.matchStatesWithBase(metadata, base)
|
||||||
|
}
|
||||||
|
if matcher, isStateMatcher := rule.(ruleStateMatcher); isStateMatcher {
|
||||||
|
return matcher.matchStates(metadata).withBase(base)
|
||||||
|
}
|
||||||
|
if rule.Match(metadata) {
|
||||||
|
return emptyRuleMatchState().withBase(base)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchRuleItemStates(item RuleItem, metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||||
|
return matchRuleItemStatesWithBase(item, metadata, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchRuleItemStatesWithBase(item RuleItem, metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet {
|
||||||
|
if matcher, isStateMatcher := item.(ruleStateMatcherWithBase); isStateMatcher {
|
||||||
|
return matcher.matchStatesWithBase(metadata, base)
|
||||||
|
}
|
||||||
|
if matcher, isStateMatcher := item.(ruleStateMatcher); isStateMatcher {
|
||||||
|
return matcher.matchStates(metadata).withBase(base)
|
||||||
|
}
|
||||||
|
if item.Match(metadata) {
|
||||||
|
return emptyRuleMatchState().withBase(base)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -52,88 +52,124 @@ func (r *abstractDefaultRule) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
return !r.matchStates(metadata).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) destinationIPCIDRMatchesSource(metadata *adapter.InboundContext) bool {
|
||||||
|
return !metadata.IgnoreDestinationIPCIDRMatch && metadata.IPCIDRMatchSource && len(r.destinationIPCIDRItems) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) destinationIPCIDRMatchesDestination(metadata *adapter.InboundContext) bool {
|
||||||
|
return !metadata.IgnoreDestinationIPCIDRMatch && !metadata.IPCIDRMatchSource && len(r.destinationIPCIDRItems) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) requiresSourceAddressMatch(metadata *adapter.InboundContext) bool {
|
||||||
|
return len(r.sourceAddressItems) > 0 || r.destinationIPCIDRMatchesSource(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) requiresDestinationAddressMatch(metadata *adapter.InboundContext) bool {
|
||||||
|
return len(r.destinationAddressItems) > 0 || r.destinationIPCIDRMatchesDestination(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||||
|
return r.matchStatesWithBase(metadata, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) matchStatesWithBase(metadata *adapter.InboundContext, inheritedBase ruleMatchState) ruleMatchStateSet {
|
||||||
if len(r.allItems) == 0 {
|
if len(r.allItems) == 0 {
|
||||||
return true
|
return emptyRuleMatchState().withBase(inheritedBase)
|
||||||
}
|
}
|
||||||
|
evaluationBase := inheritedBase
|
||||||
if len(r.sourceAddressItems) > 0 && !metadata.SourceAddressMatch {
|
if r.invert {
|
||||||
|
evaluationBase = 0
|
||||||
|
}
|
||||||
|
baseState := evaluationBase
|
||||||
|
if len(r.sourceAddressItems) > 0 {
|
||||||
metadata.DidMatch = true
|
metadata.DidMatch = true
|
||||||
for _, item := range r.sourceAddressItems {
|
if matchAnyItem(r.sourceAddressItems, metadata) {
|
||||||
if item.Match(metadata) {
|
baseState |= ruleMatchSourceAddress
|
||||||
metadata.SourceAddressMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if r.destinationIPCIDRMatchesSource(metadata) && !baseState.has(ruleMatchSourceAddress) {
|
||||||
if len(r.sourcePortItems) > 0 && !metadata.SourcePortMatch {
|
|
||||||
metadata.DidMatch = true
|
metadata.DidMatch = true
|
||||||
for _, item := range r.sourcePortItems {
|
if matchAnyItem(r.destinationIPCIDRItems, metadata) {
|
||||||
if item.Match(metadata) {
|
baseState |= ruleMatchSourceAddress
|
||||||
metadata.SourcePortMatch = true
|
}
|
||||||
break
|
} else if r.destinationIPCIDRMatchesSource(metadata) {
|
||||||
}
|
metadata.DidMatch = true
|
||||||
|
}
|
||||||
|
if len(r.sourcePortItems) > 0 {
|
||||||
|
metadata.DidMatch = true
|
||||||
|
if matchAnyItem(r.sourcePortItems, metadata) {
|
||||||
|
baseState |= ruleMatchSourcePort
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(r.destinationAddressItems) > 0 {
|
||||||
if len(r.destinationAddressItems) > 0 && !metadata.DestinationAddressMatch {
|
|
||||||
metadata.DidMatch = true
|
metadata.DidMatch = true
|
||||||
for _, item := range r.destinationAddressItems {
|
if matchAnyItem(r.destinationAddressItems, metadata) {
|
||||||
if item.Match(metadata) {
|
baseState |= ruleMatchDestinationAddress
|
||||||
metadata.DestinationAddressMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if r.destinationIPCIDRMatchesDestination(metadata) && !baseState.has(ruleMatchDestinationAddress) {
|
||||||
if !metadata.IgnoreDestinationIPCIDRMatch && len(r.destinationIPCIDRItems) > 0 && !metadata.DestinationAddressMatch {
|
|
||||||
metadata.DidMatch = true
|
metadata.DidMatch = true
|
||||||
for _, item := range r.destinationIPCIDRItems {
|
if matchAnyItem(r.destinationIPCIDRItems, metadata) {
|
||||||
if item.Match(metadata) {
|
baseState |= ruleMatchDestinationAddress
|
||||||
metadata.DestinationAddressMatch = true
|
}
|
||||||
break
|
} else if r.destinationIPCIDRMatchesDestination(metadata) {
|
||||||
}
|
metadata.DidMatch = true
|
||||||
|
}
|
||||||
|
if len(r.destinationPortItems) > 0 {
|
||||||
|
metadata.DidMatch = true
|
||||||
|
if matchAnyItem(r.destinationPortItems, metadata) {
|
||||||
|
baseState |= ruleMatchDestinationPort
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.destinationPortItems) > 0 && !metadata.DestinationPortMatch {
|
|
||||||
metadata.DidMatch = true
|
|
||||||
for _, item := range r.destinationPortItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
metadata.DestinationPortMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, item := range r.items {
|
for _, item := range r.items {
|
||||||
metadata.DidMatch = true
|
metadata.DidMatch = true
|
||||||
if !item.Match(metadata) {
|
if !item.Match(metadata) {
|
||||||
return r.invert
|
return r.invertedFailure(inheritedBase)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var stateSet ruleMatchStateSet
|
||||||
if len(r.sourceAddressItems) > 0 && !metadata.SourceAddressMatch {
|
if r.ruleSetItem != nil {
|
||||||
return r.invert
|
metadata.DidMatch = true
|
||||||
|
stateSet = matchRuleItemStatesWithBase(r.ruleSetItem, metadata, baseState)
|
||||||
|
} else {
|
||||||
|
stateSet = singleRuleMatchState(baseState)
|
||||||
}
|
}
|
||||||
|
stateSet = stateSet.filter(func(state ruleMatchState) bool {
|
||||||
if len(r.sourcePortItems) > 0 && !metadata.SourcePortMatch {
|
if r.requiresSourceAddressMatch(metadata) && !state.has(ruleMatchSourceAddress) {
|
||||||
return r.invert
|
return false
|
||||||
}
|
}
|
||||||
|
if len(r.sourcePortItems) > 0 && !state.has(ruleMatchSourcePort) {
|
||||||
if ((!metadata.IgnoreDestinationIPCIDRMatch && len(r.destinationIPCIDRItems) > 0) || len(r.destinationAddressItems) > 0) && !metadata.DestinationAddressMatch {
|
return false
|
||||||
return r.invert
|
}
|
||||||
}
|
if r.requiresDestinationAddressMatch(metadata) && !state.has(ruleMatchDestinationAddress) {
|
||||||
|
return false
|
||||||
if len(r.destinationPortItems) > 0 && !metadata.DestinationPortMatch {
|
}
|
||||||
return r.invert
|
if len(r.destinationPortItems) > 0 && !state.has(ruleMatchDestinationPort) {
|
||||||
}
|
return false
|
||||||
|
}
|
||||||
if !metadata.DidMatch {
|
|
||||||
return true
|
return true
|
||||||
|
})
|
||||||
|
if stateSet.isEmpty() {
|
||||||
|
return r.invertedFailure(inheritedBase)
|
||||||
}
|
}
|
||||||
|
if r.invert {
|
||||||
|
// DNS pre-lookup defers destination address-limit checks until the response phase.
|
||||||
|
if metadata.IgnoreDestinationIPCIDRMatch && stateSet == emptyRuleMatchState() && !metadata.DidMatch && len(r.destinationIPCIDRItems) > 0 {
|
||||||
|
return emptyRuleMatchState().withBase(inheritedBase)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return stateSet
|
||||||
|
}
|
||||||
|
|
||||||
return !r.invert
|
func (r *abstractDefaultRule) invertedFailure(base ruleMatchState) ruleMatchStateSet {
|
||||||
|
if r.invert {
|
||||||
|
return emptyRuleMatchState().withBase(base)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *abstractDefaultRule) Action() adapter.RuleAction {
|
func (r *abstractDefaultRule) Action() adapter.RuleAction {
|
||||||
@@ -191,17 +227,50 @@ func (r *abstractLogicalRule) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *abstractLogicalRule) Match(metadata *adapter.InboundContext) bool {
|
func (r *abstractLogicalRule) Match(metadata *adapter.InboundContext) bool {
|
||||||
if r.mode == C.LogicalTypeAnd {
|
return !r.matchStates(metadata).isEmpty()
|
||||||
return common.All(r.rules, func(it adapter.HeadlessRule) bool {
|
}
|
||||||
metadata.ResetRuleCache()
|
|
||||||
return it.Match(metadata)
|
func (r *abstractLogicalRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||||
}) != r.invert
|
return r.matchStatesWithBase(metadata, 0)
|
||||||
} else {
|
}
|
||||||
return common.Any(r.rules, func(it adapter.HeadlessRule) bool {
|
|
||||||
metadata.ResetRuleCache()
|
func (r *abstractLogicalRule) matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet {
|
||||||
return it.Match(metadata)
|
evaluationBase := base
|
||||||
}) != r.invert
|
if r.invert {
|
||||||
|
evaluationBase = 0
|
||||||
}
|
}
|
||||||
|
var stateSet ruleMatchStateSet
|
||||||
|
if r.mode == C.LogicalTypeAnd {
|
||||||
|
stateSet = emptyRuleMatchState().withBase(evaluationBase)
|
||||||
|
for _, rule := range r.rules {
|
||||||
|
nestedMetadata := *metadata
|
||||||
|
nestedMetadata.ResetRuleCache()
|
||||||
|
nestedStateSet := matchHeadlessRuleStatesWithBase(rule, &nestedMetadata, evaluationBase)
|
||||||
|
if nestedStateSet.isEmpty() {
|
||||||
|
if r.invert {
|
||||||
|
return emptyRuleMatchState().withBase(base)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
stateSet = stateSet.combine(nestedStateSet)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, rule := range r.rules {
|
||||||
|
nestedMetadata := *metadata
|
||||||
|
nestedMetadata.ResetRuleCache()
|
||||||
|
stateSet = stateSet.merge(matchHeadlessRuleStatesWithBase(rule, &nestedMetadata, evaluationBase))
|
||||||
|
}
|
||||||
|
if stateSet.isEmpty() {
|
||||||
|
if r.invert {
|
||||||
|
return emptyRuleMatchState().withBase(base)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.invert {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return stateSet
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *abstractLogicalRule) Action() adapter.RuleAction {
|
func (r *abstractLogicalRule) Action() adapter.RuleAction {
|
||||||
@@ -222,3 +291,13 @@ func (r *abstractLogicalRule) String() string {
|
|||||||
return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")"
|
return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func matchAnyItem(items []RuleItem, metadata *adapter.InboundContext) bool {
|
||||||
|
return common.Any(items, func(it RuleItem) bool {
|
||||||
|
return it.Match(metadata)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s ruleMatchState) has(target ruleMatchState) bool {
|
||||||
|
return s&target != 0
|
||||||
|
}
|
||||||
|
|||||||
@@ -78,9 +78,9 @@ func newRuleSetOnlyRule(ruleSetMatched bool, invert bool) *DefaultRule {
|
|||||||
}
|
}
|
||||||
return &DefaultRule{
|
return &DefaultRule{
|
||||||
abstractDefaultRule: abstractDefaultRule{
|
abstractDefaultRule: abstractDefaultRule{
|
||||||
items: []RuleItem{ruleSetItem},
|
ruleSetItem: ruleSetItem,
|
||||||
allItems: []RuleItem{ruleSetItem},
|
allItems: []RuleItem{ruleSetItem},
|
||||||
invert: invert,
|
invert: invert,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ type DefaultRule struct {
|
|||||||
abstractDefaultRule
|
abstractDefaultRule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *DefaultRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||||
|
return r.abstractDefaultRule.matchStates(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
type RuleItem interface {
|
type RuleItem interface {
|
||||||
Match(metadata *adapter.InboundContext) bool
|
Match(metadata *adapter.InboundContext) bool
|
||||||
String() string
|
String() string
|
||||||
@@ -285,7 +289,7 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio
|
|||||||
matchSource = true
|
matchSource = true
|
||||||
}
|
}
|
||||||
item := NewRuleSetItem(router, options.RuleSet, matchSource, false)
|
item := NewRuleSetItem(router, options.RuleSet, matchSource, false)
|
||||||
rule.items = append(rule.items, item)
|
rule.ruleSetItem = item
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
return rule, nil
|
return rule, nil
|
||||||
@@ -297,6 +301,10 @@ type LogicalRule struct {
|
|||||||
abstractLogicalRule
|
abstractLogicalRule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LogicalRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||||
|
return r.abstractLogicalRule.matchStates(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
func NewLogicalRule(ctx context.Context, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) {
|
func NewLogicalRule(ctx context.Context, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) {
|
||||||
action, err := NewRuleAction(ctx, logger, options.RuleAction)
|
action, err := NewRuleAction(ctx, logger, options.RuleAction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ type DefaultDNSRule struct {
|
|||||||
abstractDefaultRule
|
abstractDefaultRule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *DefaultDNSRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||||
|
return r.abstractDefaultRule.matchStates(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) {
|
func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) {
|
||||||
rule := &DefaultDNSRule{
|
rule := &DefaultDNSRule{
|
||||||
abstractDefaultRule: abstractDefaultRule{
|
abstractDefaultRule: abstractDefaultRule{
|
||||||
@@ -281,7 +285,7 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op
|
|||||||
matchSource = true
|
matchSource = true
|
||||||
}
|
}
|
||||||
item := NewRuleSetItem(router, options.RuleSet, matchSource, options.RuleSetIPCIDRAcceptEmpty)
|
item := NewRuleSetItem(router, options.RuleSet, matchSource, options.RuleSetIPCIDRAcceptEmpty)
|
||||||
rule.items = append(rule.items, item)
|
rule.ruleSetItem = item
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
return rule, nil
|
return rule, nil
|
||||||
@@ -295,12 +299,9 @@ func (r *DefaultDNSRule) WithAddressLimit() bool {
|
|||||||
if len(r.destinationIPCIDRItems) > 0 {
|
if len(r.destinationIPCIDRItems) > 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, rawRule := range r.items {
|
if r.ruleSetItem != nil {
|
||||||
ruleSet, isRuleSet := rawRule.(*RuleSetItem)
|
ruleSet, isRuleSet := r.ruleSetItem.(*RuleSetItem)
|
||||||
if !isRuleSet {
|
if isRuleSet && ruleSet.ContainsDestinationIPCIDRRule() {
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ruleSet.ContainsDestinationIPCIDRRule() {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,11 +313,11 @@ func (r *DefaultDNSRule) Match(metadata *adapter.InboundContext) bool {
|
|||||||
defer func() {
|
defer func() {
|
||||||
metadata.IgnoreDestinationIPCIDRMatch = false
|
metadata.IgnoreDestinationIPCIDRMatch = false
|
||||||
}()
|
}()
|
||||||
return r.abstractDefaultRule.Match(metadata)
|
return !r.matchStates(metadata).isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DefaultDNSRule) MatchAddressLimit(metadata *adapter.InboundContext) bool {
|
func (r *DefaultDNSRule) MatchAddressLimit(metadata *adapter.InboundContext) bool {
|
||||||
return r.abstractDefaultRule.Match(metadata)
|
return !r.matchStates(metadata).isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ adapter.DNSRule = (*LogicalDNSRule)(nil)
|
var _ adapter.DNSRule = (*LogicalDNSRule)(nil)
|
||||||
@@ -325,6 +326,10 @@ type LogicalDNSRule struct {
|
|||||||
abstractLogicalRule
|
abstractLogicalRule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LogicalDNSRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||||
|
return r.abstractLogicalRule.matchStates(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
func NewLogicalDNSRule(ctx context.Context, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) {
|
func NewLogicalDNSRule(ctx context.Context, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) {
|
||||||
r := &LogicalDNSRule{
|
r := &LogicalDNSRule{
|
||||||
abstractLogicalRule: abstractLogicalRule{
|
abstractLogicalRule: abstractLogicalRule{
|
||||||
@@ -372,29 +377,13 @@ func (r *LogicalDNSRule) WithAddressLimit() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *LogicalDNSRule) Match(metadata *adapter.InboundContext) bool {
|
func (r *LogicalDNSRule) Match(metadata *adapter.InboundContext) bool {
|
||||||
if r.mode == C.LogicalTypeAnd {
|
metadata.IgnoreDestinationIPCIDRMatch = true
|
||||||
return common.All(r.rules, func(it adapter.HeadlessRule) bool {
|
defer func() {
|
||||||
metadata.ResetRuleCache()
|
metadata.IgnoreDestinationIPCIDRMatch = false
|
||||||
return it.(adapter.DNSRule).Match(metadata)
|
}()
|
||||||
}) != r.invert
|
return !r.matchStates(metadata).isEmpty()
|
||||||
} 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 {
|
func (r *LogicalDNSRule) MatchAddressLimit(metadata *adapter.InboundContext) bool {
|
||||||
if r.mode == C.LogicalTypeAnd {
|
return !r.matchStates(metadata).isEmpty()
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ type DefaultHeadlessRule struct {
|
|||||||
abstractDefaultRule
|
abstractDefaultRule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *DefaultHeadlessRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||||
|
return r.abstractDefaultRule.matchStates(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessRule) (*DefaultHeadlessRule, error) {
|
func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessRule) (*DefaultHeadlessRule, error) {
|
||||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||||
rule := &DefaultHeadlessRule{
|
rule := &DefaultHeadlessRule{
|
||||||
@@ -41,6 +45,11 @@ func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessR
|
|||||||
invert: options.Invert,
|
invert: options.Invert,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
if len(options.QueryType) > 0 {
|
||||||
|
item := NewQueryTypeItem(options.QueryType)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
if len(options.Network) > 0 {
|
if len(options.Network) > 0 {
|
||||||
item := NewNetworkItem(options.Network)
|
item := NewNetworkItem(options.Network)
|
||||||
rule.items = append(rule.items, item)
|
rule.items = append(rule.items, item)
|
||||||
@@ -199,6 +208,10 @@ type LogicalHeadlessRule struct {
|
|||||||
abstractLogicalRule
|
abstractLogicalRule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LogicalHeadlessRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||||
|
return r.abstractLogicalRule.matchStates(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
func NewLogicalHeadlessRule(ctx context.Context, options option.LogicalHeadlessRule) (*LogicalHeadlessRule, error) {
|
func NewLogicalHeadlessRule(ctx context.Context, options option.LogicalHeadlessRule) (*LogicalHeadlessRule, error) {
|
||||||
r := &LogicalHeadlessRule{
|
r := &LogicalHeadlessRule{
|
||||||
abstractLogicalRule{
|
abstractLogicalRule{
|
||||||
|
|||||||
@@ -41,16 +41,23 @@ func (r *RuleSetItem) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuleSetItem) Match(metadata *adapter.InboundContext) bool {
|
func (r *RuleSetItem) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
return !r.matchStates(metadata).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RuleSetItem) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||||
|
return r.matchStatesWithBase(metadata, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RuleSetItem) matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet {
|
||||||
|
var stateSet ruleMatchStateSet
|
||||||
for _, ruleSet := range r.setList {
|
for _, ruleSet := range r.setList {
|
||||||
nestedMetadata := *metadata
|
nestedMetadata := *metadata
|
||||||
nestedMetadata.ResetRuleMatchCache()
|
nestedMetadata.ResetRuleMatchCache()
|
||||||
nestedMetadata.IPCIDRMatchSource = r.ipCidrMatchSource
|
nestedMetadata.IPCIDRMatchSource = r.ipCidrMatchSource
|
||||||
nestedMetadata.IPCIDRAcceptEmpty = r.ipCidrAcceptEmpty
|
nestedMetadata.IPCIDRAcceptEmpty = r.ipCidrAcceptEmpty
|
||||||
if ruleSet.Match(&nestedMetadata) {
|
stateSet = stateSet.merge(matchHeadlessRuleStatesWithBase(ruleSet, &nestedMetadata, base))
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false
|
return stateSet
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuleSetItem) ContainsDestinationIPCIDRRule() bool {
|
func (r *RuleSetItem) ContainsDestinationIPCIDRRule() bool {
|
||||||
|
|||||||
@@ -202,12 +202,19 @@ func (s *LocalRuleSet) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalRuleSet) Match(metadata *adapter.InboundContext) bool {
|
func (s *LocalRuleSet) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
return !s.matchStates(metadata).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LocalRuleSet) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||||
|
return s.matchStatesWithBase(metadata, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LocalRuleSet) matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet {
|
||||||
|
var stateSet ruleMatchStateSet
|
||||||
for _, rule := range s.rules {
|
for _, rule := range s.rules {
|
||||||
nestedMetadata := *metadata
|
nestedMetadata := *metadata
|
||||||
nestedMetadata.ResetRuleMatchCache()
|
nestedMetadata.ResetRuleMatchCache()
|
||||||
if rule.Match(&nestedMetadata) {
|
stateSet = stateSet.merge(matchHeadlessRuleStatesWithBase(rule, &nestedMetadata, base))
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false
|
return stateSet
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -322,12 +322,19 @@ func (s *RemoteRuleSet) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *RemoteRuleSet) Match(metadata *adapter.InboundContext) bool {
|
func (s *RemoteRuleSet) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
return !s.matchStates(metadata).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteRuleSet) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||||
|
return s.matchStatesWithBase(metadata, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteRuleSet) matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet {
|
||||||
|
var stateSet ruleMatchStateSet
|
||||||
for _, rule := range s.rules {
|
for _, rule := range s.rules {
|
||||||
nestedMetadata := *metadata
|
nestedMetadata := *metadata
|
||||||
nestedMetadata.ResetRuleMatchCache()
|
nestedMetadata.ResetRuleMatchCache()
|
||||||
if rule.Match(&nestedMetadata) {
|
stateSet = stateSet.merge(matchHeadlessRuleStatesWithBase(rule, &nestedMetadata, base))
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false
|
return stateSet
|
||||||
}
|
}
|
||||||
|
|||||||
852
route/rule/rule_set_semantics_test.go
Normal file
852
route/rule/rule_set_semantics_test.go
Normal file
@@ -0,0 +1,852 @@
|
|||||||
|
package rule
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/convertor/adguard"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
slogger "github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRouteRuleSetMergeDestinationAddressGroup(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
metadata adapter.InboundContext
|
||||||
|
inner adapter.HeadlessRule
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "domain",
|
||||||
|
metadata: testMetadata("www.example.com"),
|
||||||
|
inner: headlessDefaultRule(t, func(rule *abstractDefaultRule) { addDestinationAddressItem(t, rule, []string{"www.example.com"}, nil) }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "domain_suffix",
|
||||||
|
metadata: testMetadata("www.example.com"),
|
||||||
|
inner: headlessDefaultRule(t, func(rule *abstractDefaultRule) { addDestinationAddressItem(t, rule, nil, []string{"example.com"}) }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "domain_keyword",
|
||||||
|
metadata: testMetadata("www.example.com"),
|
||||||
|
inner: headlessDefaultRule(t, func(rule *abstractDefaultRule) { addDestinationKeywordItem(rule, []string{"example"}) }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "domain_regex",
|
||||||
|
metadata: testMetadata("www.example.com"),
|
||||||
|
inner: headlessDefaultRule(t, func(rule *abstractDefaultRule) { addDestinationRegexItem(t, rule, []string{`^www\.example\.com$`}) }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ip_cidr",
|
||||||
|
metadata: func() adapter.InboundContext {
|
||||||
|
metadata := testMetadata("lookup.example")
|
||||||
|
metadata.DestinationAddresses = []netip.Addr{netip.MustParseAddr("8.8.8.8")}
|
||||||
|
return metadata
|
||||||
|
}(),
|
||||||
|
inner: headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"8.8.8.0/24"})
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ruleSet := newLocalRuleSetForTest("merge-destination", testCase.inner)
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&testCase.metadata))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRouteRuleSetMergeSourceAndPortGroups(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Run("source address", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("merge-source-address", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addSourceAddressItem(t, rule, []string{"10.0.0.0/8"})
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
addSourceAddressItem(t, rule, []string{"198.51.100.0/24"})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("source address via ruleset ipcidr match source", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("merge-source-address-ipcidr", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"10.0.0.0/8"})
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{
|
||||||
|
setList: []adapter.RuleSet{ruleSet},
|
||||||
|
ipCidrMatchSource: true,
|
||||||
|
})
|
||||||
|
addSourceAddressItem(t, rule, []string{"198.51.100.0/24"})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("destination port", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("merge-destination-port", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationPortItem(rule, []uint16{443})
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
addDestinationPortItem(rule, []uint16{8443})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("destination port range", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("merge-destination-port-range", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationPortRangeItem(t, rule, []string{"400:500"})
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
addDestinationPortItem(rule, []uint16{8443})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("source port", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("merge-source-port", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addSourcePortItem(rule, []uint16{1000})
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
addSourcePortItem(rule, []uint16{2000})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("source port range", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("merge-source-port-range", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addSourcePortRangeItem(t, rule, []string{"900:1100"})
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
addSourcePortItem(rule, []uint16{2000})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRouteRuleSetOuterGroupedStateMergesIntoSameGroup(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
metadata adapter.InboundContext
|
||||||
|
buildOuter func(*testing.T, *abstractDefaultRule)
|
||||||
|
buildInner func(*testing.T, *abstractDefaultRule)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "destination address",
|
||||||
|
metadata: testMetadata("www.example.com"),
|
||||||
|
buildOuter: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
},
|
||||||
|
buildInner: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"google.com"})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "source address",
|
||||||
|
metadata: testMetadata("www.example.com"),
|
||||||
|
buildOuter: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addSourceAddressItem(t, rule, []string{"10.0.0.0/8"})
|
||||||
|
},
|
||||||
|
buildInner: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addSourceAddressItem(t, rule, []string{"198.51.100.0/24"})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "source port",
|
||||||
|
metadata: testMetadata("www.example.com"),
|
||||||
|
buildOuter: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addSourcePortItem(rule, []uint16{1000})
|
||||||
|
},
|
||||||
|
buildInner: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addSourcePortItem(rule, []uint16{2000})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "destination port",
|
||||||
|
metadata: testMetadata("www.example.com"),
|
||||||
|
buildOuter: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addDestinationPortItem(rule, []uint16{443})
|
||||||
|
},
|
||||||
|
buildInner: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addDestinationPortItem(rule, []uint16{8443})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "destination ip cidr",
|
||||||
|
metadata: func() adapter.InboundContext {
|
||||||
|
metadata := testMetadata("lookup.example")
|
||||||
|
metadata.DestinationAddresses = []netip.Addr{netip.MustParseAddr("203.0.113.1")}
|
||||||
|
return metadata
|
||||||
|
}(),
|
||||||
|
buildOuter: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||||
|
},
|
||||||
|
buildInner: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"198.51.100.0/24"})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ruleSet := newLocalRuleSetForTest("outer-merge-"+testCase.name, headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
testCase.buildInner(t, rule)
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
testCase.buildOuter(t, rule)
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&testCase.metadata))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRouteRuleSetOtherFieldsStayAnd(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("other-fields-and", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP}))
|
||||||
|
})
|
||||||
|
require.False(t, rule.Match(&metadata))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRouteRuleSetMergedBranchKeepsAndConstraints(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Run("outer group does not bypass inner non grouped condition", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("network-and", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP}))
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.False(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("outer group does not satisfy different grouped branch", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("different-group", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"google.com"})
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addSourcePortItem(rule, []uint16{1000})
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.False(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRouteRuleSetOrSemantics(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Run("later ruleset can satisfy outer group", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
emptyStateSet := newLocalRuleSetForTest("network-only", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP}))
|
||||||
|
}))
|
||||||
|
destinationStateSet := newLocalRuleSetForTest("domain-only", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{emptyStateSet, destinationStateSet}})
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("later rule in same set can satisfy outer group", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest(
|
||||||
|
"rule-set-or",
|
||||||
|
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP}))
|
||||||
|
}),
|
||||||
|
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("cross ruleset union is not allowed", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
sourceStateSet := newLocalRuleSetForTest("source-only", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addSourcePortItem(rule, []uint16{1000})
|
||||||
|
}))
|
||||||
|
destinationStateSet := newLocalRuleSetForTest("destination-only", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{sourceStateSet, destinationStateSet}})
|
||||||
|
addSourcePortItem(rule, []uint16{2000})
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||||
|
})
|
||||||
|
require.False(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRouteRuleSetLogicalSemantics(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Run("logical or keeps all successful branch states", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("logical-or", headlessLogicalRule(
|
||||||
|
C.LogicalTypeOr,
|
||||||
|
false,
|
||||||
|
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP}))
|
||||||
|
}),
|
||||||
|
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("logical and unions child states", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("logical-and", headlessLogicalRule(
|
||||||
|
C.LogicalTypeAnd,
|
||||||
|
false,
|
||||||
|
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
}),
|
||||||
|
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addSourcePortItem(rule, []uint16{1000})
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||||
|
addSourcePortItem(rule, []uint16{2000})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("invert success does not contribute positive state", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("invert", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
rule.invert = true
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"cn"})
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||||
|
})
|
||||||
|
require.False(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRouteRuleSetInvertMergedBranchSemantics(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Run("default invert keeps inherited group outside grouped predicate", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("invert-grouped", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
rule.invert = true
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"google.com"})
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("default invert keeps inherited group after negation succeeds", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("invert-network", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
rule.invert = true
|
||||||
|
addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP}))
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("logical invert keeps inherited group outside grouped predicate", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("logical-invert-grouped", headlessLogicalRule(
|
||||||
|
C.LogicalTypeOr,
|
||||||
|
true,
|
||||||
|
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"google.com"})
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("logical invert keeps inherited group after negation succeeds", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("logical-invert-network", headlessLogicalRule(
|
||||||
|
C.LogicalTypeOr,
|
||||||
|
true,
|
||||||
|
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP}))
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRouteRuleSetNoLeakageRegressions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Run("same ruleset failed branch does not leak", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest(
|
||||||
|
"same-set",
|
||||||
|
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
addSourcePortItem(rule, []uint16{1})
|
||||||
|
}),
|
||||||
|
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||||
|
addSourcePortItem(rule, []uint16{1000})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.False(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("adguard exclusion remains isolated across rulesets", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("im.qq.com")
|
||||||
|
excludeSet := newLocalRuleSetForTest("adguard", mustAdGuardRule(t, "@@||im.qq.com^\n||whatever1.com^\n"))
|
||||||
|
otherSet := newLocalRuleSetForTest("other", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"whatever2.com"})
|
||||||
|
}))
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{excludeSet, otherSet}})
|
||||||
|
})
|
||||||
|
require.False(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultRuleDoesNotReuseGroupedMatchCacheAcrossEvaluations(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
|
||||||
|
metadata.Destination.Fqdn = "www.example.org"
|
||||||
|
require.False(t, rule.Match(&metadata))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRouteRuleSetRemoteUsesSameSemantics(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newRemoteRuleSetForTest(
|
||||||
|
"remote",
|
||||||
|
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP}))
|
||||||
|
}),
|
||||||
|
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
rule := routeRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDNSRuleSetSemantics(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Run("outer destination group merges into matching ruleset branch", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.baidu.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("dns-merged-branch", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"google.com"})
|
||||||
|
}))
|
||||||
|
rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"baidu.com"})
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("outer destination group does not bypass ruleset non grouped condition", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("dns-network-and", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP}))
|
||||||
|
}))
|
||||||
|
rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.False(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("outer destination group stays outside inverted grouped branch", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.baidu.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("dns-invert-grouped", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
rule.invert = true
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"google.com"})
|
||||||
|
}))
|
||||||
|
rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"baidu.com"})
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("outer destination group stays outside inverted logical branch", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("dns-logical-invert-network", headlessLogicalRule(
|
||||||
|
C.LogicalTypeOr,
|
||||||
|
true,
|
||||||
|
headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP}))
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.True(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("match address limit merges destination group", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("dns-merge", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
}))
|
||||||
|
rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||||
|
})
|
||||||
|
require.True(t, rule.MatchAddressLimit(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("dns keeps ruleset or semantics", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
emptyStateSet := newLocalRuleSetForTest("dns-empty", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP}))
|
||||||
|
}))
|
||||||
|
destinationStateSet := newLocalRuleSetForTest("dns-destination", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationAddressItem(t, rule, nil, []string{"example.com"})
|
||||||
|
}))
|
||||||
|
rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{emptyStateSet, destinationStateSet}})
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||||
|
})
|
||||||
|
require.True(t, rule.MatchAddressLimit(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("ruleset ip cidr flags stay scoped", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("www.example.com")
|
||||||
|
ruleSet := newLocalRuleSetForTest("dns-ipcidr", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||||
|
}))
|
||||||
|
rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{
|
||||||
|
setList: []adapter.RuleSet{ruleSet},
|
||||||
|
ipCidrAcceptEmpty: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
require.True(t, rule.MatchAddressLimit(&metadata))
|
||||||
|
require.False(t, metadata.IPCIDRMatchSource)
|
||||||
|
require.False(t, metadata.IPCIDRAcceptEmpty)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDNSInvertAddressLimitPreLookupRegression(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
build func(*testing.T, *abstractDefaultRule)
|
||||||
|
matchedAddrs []netip.Addr
|
||||||
|
unmatchedAddrs []netip.Addr
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ip_cidr",
|
||||||
|
build: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||||
|
},
|
||||||
|
matchedAddrs: []netip.Addr{netip.MustParseAddr("203.0.113.1")},
|
||||||
|
unmatchedAddrs: []netip.Addr{netip.MustParseAddr("8.8.8.8")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ip_is_private",
|
||||||
|
build: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addDestinationIPIsPrivateItem(rule)
|
||||||
|
},
|
||||||
|
matchedAddrs: []netip.Addr{netip.MustParseAddr("10.0.0.1")},
|
||||||
|
unmatchedAddrs: []netip.Addr{netip.MustParseAddr("8.8.8.8")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ip_accept_any",
|
||||||
|
build: func(t *testing.T, rule *abstractDefaultRule) {
|
||||||
|
t.Helper()
|
||||||
|
addDestinationIPAcceptAnyItem(rule)
|
||||||
|
},
|
||||||
|
matchedAddrs: []netip.Addr{netip.MustParseAddr("203.0.113.1")},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
rule.invert = true
|
||||||
|
testCase.build(t, rule)
|
||||||
|
})
|
||||||
|
|
||||||
|
preLookupMetadata := testMetadata("lookup.example")
|
||||||
|
require.True(t, rule.Match(&preLookupMetadata))
|
||||||
|
|
||||||
|
matchedMetadata := testMetadata("lookup.example")
|
||||||
|
matchedMetadata.DestinationAddresses = testCase.matchedAddrs
|
||||||
|
require.False(t, rule.MatchAddressLimit(&matchedMetadata))
|
||||||
|
|
||||||
|
unmatchedMetadata := testMetadata("lookup.example")
|
||||||
|
unmatchedMetadata.DestinationAddresses = testCase.unmatchedAddrs
|
||||||
|
require.True(t, rule.MatchAddressLimit(&unmatchedMetadata))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
t.Run("mixed resolved and deferred fields keep old pre lookup false", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("lookup.example")
|
||||||
|
rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
rule.invert = true
|
||||||
|
addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP}))
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||||
|
})
|
||||||
|
require.False(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
t.Run("ruleset only deferred fields keep old pre lookup false", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
metadata := testMetadata("lookup.example")
|
||||||
|
ruleSet := newLocalRuleSetForTest("dns-ruleset-ipcidr", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
|
||||||
|
addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
|
||||||
|
}))
|
||||||
|
rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
|
||||||
|
rule.invert = true
|
||||||
|
addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
|
||||||
|
})
|
||||||
|
require.False(t, rule.Match(&metadata))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func routeRuleForTest(build func(*abstractDefaultRule)) *DefaultRule {
|
||||||
|
rule := &DefaultRule{}
|
||||||
|
build(&rule.abstractDefaultRule)
|
||||||
|
return rule
|
||||||
|
}
|
||||||
|
|
||||||
|
func dnsRuleForTest(build func(*abstractDefaultRule)) *DefaultDNSRule {
|
||||||
|
rule := &DefaultDNSRule{}
|
||||||
|
build(&rule.abstractDefaultRule)
|
||||||
|
return rule
|
||||||
|
}
|
||||||
|
|
||||||
|
func headlessDefaultRule(t *testing.T, build func(*abstractDefaultRule)) *DefaultHeadlessRule {
|
||||||
|
t.Helper()
|
||||||
|
rule := &DefaultHeadlessRule{}
|
||||||
|
build(&rule.abstractDefaultRule)
|
||||||
|
return rule
|
||||||
|
}
|
||||||
|
|
||||||
|
func headlessLogicalRule(mode string, invert bool, rules ...adapter.HeadlessRule) *LogicalHeadlessRule {
|
||||||
|
return &LogicalHeadlessRule{
|
||||||
|
abstractLogicalRule: abstractLogicalRule{
|
||||||
|
rules: rules,
|
||||||
|
mode: mode,
|
||||||
|
invert: invert,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLocalRuleSetForTest(tag string, rules ...adapter.HeadlessRule) *LocalRuleSet {
|
||||||
|
return &LocalRuleSet{
|
||||||
|
tag: tag,
|
||||||
|
rules: rules,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRemoteRuleSetForTest(tag string, rules ...adapter.HeadlessRule) *RemoteRuleSet {
|
||||||
|
return &RemoteRuleSet{
|
||||||
|
options: option.RuleSet{Tag: tag},
|
||||||
|
rules: rules,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustAdGuardRule(t *testing.T, content string) adapter.HeadlessRule {
|
||||||
|
t.Helper()
|
||||||
|
rules, err := adguard.ToOptions(strings.NewReader(content), slogger.NOP())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, rules, 1)
|
||||||
|
rule, err := NewHeadlessRule(context.Background(), rules[0])
|
||||||
|
require.NoError(t, err)
|
||||||
|
return rule
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMetadata(domain string) adapter.InboundContext {
|
||||||
|
return adapter.InboundContext{
|
||||||
|
Network: N.NetworkTCP,
|
||||||
|
Source: M.Socksaddr{
|
||||||
|
Addr: netip.MustParseAddr("10.0.0.1"),
|
||||||
|
Port: 1000,
|
||||||
|
},
|
||||||
|
Destination: M.Socksaddr{
|
||||||
|
Fqdn: domain,
|
||||||
|
Port: 443,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addRuleSetItem(rule *abstractDefaultRule, item *RuleSetItem) {
|
||||||
|
rule.ruleSetItem = item
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addOtherItem(rule *abstractDefaultRule, item RuleItem) {
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addSourceAddressItem(t *testing.T, rule *abstractDefaultRule, cidrs []string) {
|
||||||
|
t.Helper()
|
||||||
|
item, err := NewIPCIDRItem(true, cidrs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
rule.sourceAddressItems = append(rule.sourceAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDestinationAddressItem(t *testing.T, rule *abstractDefaultRule, domains []string, suffixes []string) {
|
||||||
|
t.Helper()
|
||||||
|
item, err := NewDomainItem(domains, suffixes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDestinationKeywordItem(rule *abstractDefaultRule, keywords []string) {
|
||||||
|
item := NewDomainKeywordItem(keywords)
|
||||||
|
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDestinationRegexItem(t *testing.T, rule *abstractDefaultRule, regexes []string) {
|
||||||
|
t.Helper()
|
||||||
|
item, err := NewDomainRegexItem(regexes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDestinationIPCIDRItem(t *testing.T, rule *abstractDefaultRule, cidrs []string) {
|
||||||
|
t.Helper()
|
||||||
|
item, err := NewIPCIDRItem(false, cidrs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDestinationIPIsPrivateItem(rule *abstractDefaultRule) {
|
||||||
|
item := NewIPIsPrivateItem(false)
|
||||||
|
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDestinationIPAcceptAnyItem(rule *abstractDefaultRule) {
|
||||||
|
item := NewIPAcceptAnyItem()
|
||||||
|
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addSourcePortItem(rule *abstractDefaultRule, ports []uint16) {
|
||||||
|
item := NewPortItem(true, ports)
|
||||||
|
rule.sourcePortItems = append(rule.sourcePortItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addSourcePortRangeItem(t *testing.T, rule *abstractDefaultRule, ranges []string) {
|
||||||
|
t.Helper()
|
||||||
|
item, err := NewPortRangeItem(true, ranges)
|
||||||
|
require.NoError(t, err)
|
||||||
|
rule.sourcePortItems = append(rule.sourcePortItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDestinationPortItem(rule *abstractDefaultRule, ports []uint16) {
|
||||||
|
item := NewPortItem(false, ports)
|
||||||
|
rule.destinationPortItems = append(rule.destinationPortItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDestinationPortRangeItem(t *testing.T, rule *abstractDefaultRule, ranges []string) {
|
||||||
|
t.Helper()
|
||||||
|
item, err := NewPortRangeItem(false, ranges)
|
||||||
|
require.NoError(t, err)
|
||||||
|
rule.destinationPortItems = append(rule.destinationPortItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
@@ -229,12 +229,13 @@ func (e *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Endpoint) Close() error {
|
func (e *Endpoint) Close() error {
|
||||||
if e.device != nil {
|
|
||||||
e.device.Close()
|
|
||||||
}
|
|
||||||
if e.pauseCallback != nil {
|
if e.pauseCallback != nil {
|
||||||
e.pause.UnregisterCallback(e.pauseCallback)
|
e.pause.UnregisterCallback(e.pauseCallback)
|
||||||
}
|
}
|
||||||
|
if e.device != nil {
|
||||||
|
e.device.Down()
|
||||||
|
e.device.Close()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user