mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-05-19 17:14:12 +08:00
Compare commits
No commits in common. "main" and "v25.2.21" have entirely different histories.
33
.github/workflows/release-win7.yml
vendored
33
.github/workflows/release-win7.yml
vendored
@ -9,38 +9,7 @@ on:
|
|||||||
types: [opened, synchronize, reopened]
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-assets:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Restore Geodat Cache
|
|
||||||
uses: actions/cache/restore@v4
|
|
||||||
with:
|
|
||||||
path: resources
|
|
||||||
key: xray-geodat-
|
|
||||||
|
|
||||||
- name: Check Assets Existence
|
|
||||||
id: check-assets
|
|
||||||
run: |
|
|
||||||
[ -d 'resources' ] || mkdir resources
|
|
||||||
LIST=('geoip.dat' 'geosite.dat')
|
|
||||||
for FILE_NAME in "${LIST[@]}"
|
|
||||||
do
|
|
||||||
echo -e "Checking ${FILE_NAME}..."
|
|
||||||
if [ -s "./resources/${FILE_NAME}" ]; then
|
|
||||||
echo -e "${FILE_NAME} exists."
|
|
||||||
else
|
|
||||||
echo -e "${FILE_NAME} does not exist."
|
|
||||||
echo "missing=true" >> $GITHUB_OUTPUT
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Sleep for 90 seconds if Assets Missing
|
|
||||||
if: steps.check-assets.outputs.missing == 'true'
|
|
||||||
run: sleep 90
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
needs: check-assets
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
strategy:
|
strategy:
|
||||||
@ -82,7 +51,7 @@ jobs:
|
|||||||
GOSDK=$(go env GOROOT)
|
GOSDK=$(go env GOROOT)
|
||||||
rm -r $GOSDK/*
|
rm -r $GOSDK/*
|
||||||
cd $GOSDK
|
cd $GOSDK
|
||||||
curl -O -L -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" https://github.com/XTLS/go-win7/releases/latest/download/go-for-win7-linux-amd64.zip
|
curl -O -L https://github.com/XTLS/go-win7/releases/latest/download/go-for-win7-linux-amd64.zip
|
||||||
unzip ./go-for-win7-linux-amd64.zip -d $GOSDK
|
unzip ./go-for-win7-linux-amd64.zip -d $GOSDK
|
||||||
rm ./go-for-win7-linux-amd64.zip
|
rm ./go-for-win7-linux-amd64.zip
|
||||||
|
|
||||||
|
64
.github/workflows/release.yml
vendored
64
.github/workflows/release.yml
vendored
@ -9,53 +9,7 @@ on:
|
|||||||
types: [opened, synchronize, reopened]
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-assets:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Restore Geodat Cache
|
|
||||||
uses: actions/cache/restore@v4
|
|
||||||
with:
|
|
||||||
path: resources
|
|
||||||
key: xray-geodat-
|
|
||||||
|
|
||||||
- name: Check Assets Existence
|
|
||||||
id: check-assets
|
|
||||||
run: |
|
|
||||||
[ -d 'resources' ] || mkdir resources
|
|
||||||
LIST=('geoip.dat' 'geosite.dat')
|
|
||||||
for FILE_NAME in "${LIST[@]}"
|
|
||||||
do
|
|
||||||
echo -e "Checking ${FILE_NAME}..."
|
|
||||||
if [ -s "./resources/${FILE_NAME}" ]; then
|
|
||||||
echo -e "${FILE_NAME} exists."
|
|
||||||
else
|
|
||||||
echo -e "${FILE_NAME} does not exist."
|
|
||||||
echo "missing=true" >> $GITHUB_OUTPUT
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Trigger Asset Update Workflow if Assets Missing
|
|
||||||
if: steps.check-assets.outputs.missing == 'true'
|
|
||||||
uses: actions/github-script@v7
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
script: |
|
|
||||||
const { owner, repo } = context.repo;
|
|
||||||
await github.rest.actions.createWorkflowDispatch({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
workflow_id: 'scheduled-assets-update.yml',
|
|
||||||
ref: context.ref
|
|
||||||
});
|
|
||||||
console.log('Triggered scheduled-assets-update.yml due to missing assets on branch:', context.ref);
|
|
||||||
|
|
||||||
- name: Sleep for 90 seconds if Assets Missing
|
|
||||||
if: steps.check-assets.outputs.missing == 'true'
|
|
||||||
run: sleep 90
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
needs: check-assets
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
strategy:
|
strategy:
|
||||||
@ -88,11 +42,6 @@ jobs:
|
|||||||
- goos: android
|
- goos: android
|
||||||
goarch: arm64
|
goarch: arm64
|
||||||
# END Android ARM 8
|
# END Android ARM 8
|
||||||
# BEGIN Android AMD64
|
|
||||||
- goos: android
|
|
||||||
goarch: amd64
|
|
||||||
patch-assetname: android-amd64
|
|
||||||
# END Android AMD64
|
|
||||||
# Windows ARM
|
# Windows ARM
|
||||||
- goos: windows
|
- goos: windows
|
||||||
goarch: arm64
|
goarch: arm64
|
||||||
@ -155,19 +104,6 @@ jobs:
|
|||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up NDK
|
|
||||||
if: matrix.goos == 'android'
|
|
||||||
run: |
|
|
||||||
wget -qO android-ndk.zip https://dl.google.com/android/repository/android-ndk-r28b-linux.zip
|
|
||||||
unzip android-ndk.zip
|
|
||||||
rm android-ndk.zip
|
|
||||||
declare -A arches=(
|
|
||||||
["amd64"]="x86_64-linux-android24-clang"
|
|
||||||
["arm64"]="aarch64-linux-android24-clang"
|
|
||||||
)
|
|
||||||
echo CC="$(realpath android-ndk-*/toolchains/llvm/prebuilt/linux-x86_64/bin)/${arches[${{ matrix.goarch }}]}" >> $GITHUB_ENV
|
|
||||||
echo CGO_ENABLED=1 >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Show workflow information
|
- name: Show workflow information
|
||||||
run: |
|
run: |
|
||||||
_NAME=${{ matrix.patch-assetname }}
|
_NAME=${{ matrix.patch-assetname }}
|
||||||
|
@ -9,7 +9,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
# Update GeoData on every day (22:30 UTC)
|
# Update GeoData on every day (22:30 UTC)
|
||||||
- cron: "30 22 * * *"
|
- cron: '30 22 * * *'
|
||||||
push:
|
push:
|
||||||
# Prevent triggering update request storm
|
# Prevent triggering update request storm
|
||||||
paths:
|
paths:
|
||||||
@ -45,12 +45,12 @@ jobs:
|
|||||||
INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3,$4}'))
|
INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3,$4}'))
|
||||||
FILE_NAME="${INFO[3]}.dat"
|
FILE_NAME="${INFO[3]}.dat"
|
||||||
echo -e "Verifying HASH key..."
|
echo -e "Verifying HASH key..."
|
||||||
HASH="$(curl -sL -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" "https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat.sha256sum" | awk -F ' ' '{print $1}')"
|
HASH="$(curl -sL "https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat.sha256sum" | awk -F ' ' '{print $1}')"
|
||||||
if [ -s "./resources/${FILE_NAME}" ] && [ "$(sha256sum "./resources/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ]; then
|
if [ -s "./resources/${FILE_NAME}" ] && [ "$(sha256sum "./resources/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ]; then
|
||||||
continue
|
continue
|
||||||
else
|
else
|
||||||
echo -e "Downloading https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat..."
|
echo -e "Downloading https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat..."
|
||||||
curl -L -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" "https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat" -o ./resources/${FILE_NAME}
|
curl -L "https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat" -o ./resources/${FILE_NAME}
|
||||||
echo -e "Verifying HASH key..."
|
echo -e "Verifying HASH key..."
|
||||||
[ "$(sha256sum "./resources/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ] || { echo -e "The HASH key of ${FILE_NAME} does not match cloud one."; exit 1; }
|
[ "$(sha256sum "./resources/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ] || { echo -e "The HASH key of ${FILE_NAME} does not match cloud one."; exit 1; }
|
||||||
echo "unhit=true" >> $GITHUB_OUTPUT
|
echo "unhit=true" >> $GITHUB_OUTPUT
|
||||||
|
29
.github/workflows/test.yml
vendored
29
.github/workflows/test.yml
vendored
@ -6,36 +6,7 @@ on:
|
|||||||
types: [opened, synchronize, reopened]
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-assets:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Restore Geodat Cache
|
|
||||||
uses: actions/cache/restore@v4
|
|
||||||
with:
|
|
||||||
path: resources
|
|
||||||
key: xray-geodat-
|
|
||||||
- name: Check Assets Existence
|
|
||||||
id: check-assets
|
|
||||||
run: |
|
|
||||||
[ -d 'resources' ] || mkdir resources
|
|
||||||
LIST=('geoip.dat' 'geosite.dat')
|
|
||||||
for FILE_NAME in "${LIST[@]}"
|
|
||||||
do
|
|
||||||
echo -e "Checking ${FILE_NAME}..."
|
|
||||||
if [ -s "./resources/${FILE_NAME}" ]; then
|
|
||||||
echo -e "${FILE_NAME} exists."
|
|
||||||
else
|
|
||||||
echo -e "${FILE_NAME} does not exist."
|
|
||||||
echo "missing=true" >> $GITHUB_OUTPUT
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
- name: Sleep for 90 seconds if Assets Missing
|
|
||||||
if: steps.check-assets.outputs.missing == 'true'
|
|
||||||
run: sleep 90
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
needs: check-assets
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
17
.gitignore
vendored
17
.gitignore
vendored
@ -14,18 +14,10 @@
|
|||||||
# Dependency directories (remove the comment below to include it)
|
# Dependency directories (remove the comment below to include it)
|
||||||
# vendor/
|
# vendor/
|
||||||
|
|
||||||
# macOS specific files
|
|
||||||
*.DS_Store
|
*.DS_Store
|
||||||
|
.idea
|
||||||
# IDE specific files
|
|
||||||
.idea/
|
|
||||||
.vscode/
|
|
||||||
|
|
||||||
# Archive files
|
|
||||||
*.zip
|
*.zip
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
|
|
||||||
# Binaries
|
|
||||||
xray
|
xray
|
||||||
xray_softfloat
|
xray_softfloat
|
||||||
mockgen
|
mockgen
|
||||||
@ -34,13 +26,8 @@ vprotogen
|
|||||||
errorgen
|
errorgen
|
||||||
!common/errors/errorgen/
|
!common/errors/errorgen/
|
||||||
*.dat
|
*.dat
|
||||||
|
.vscode
|
||||||
# Build assets
|
|
||||||
/build_assets
|
/build_assets
|
||||||
|
|
||||||
# Output from dlv test
|
# Output from dlv test
|
||||||
**/debug.*
|
**/debug.*
|
||||||
|
|
||||||
# Certificates
|
|
||||||
*.crt
|
|
||||||
*.key
|
|
||||||
|
@ -38,7 +38,6 @@
|
|||||||
- [teddysun/xray](https://hub.docker.com/r/teddysun/xray)
|
- [teddysun/xray](https://hub.docker.com/r/teddysun/xray)
|
||||||
- [wulabing/xray_docker](https://github.com/wulabing/xray_docker)
|
- [wulabing/xray_docker](https://github.com/wulabing/xray_docker)
|
||||||
- Web Panel - **WARNING: Please DO NOT USE plain HTTP panels like 3X-UI**, as they are believed to be bribed by Iran GFW for supporting plain HTTP by default and refused to change (https://github.com/XTLS/Xray-core/pull/3884#issuecomment-2439595331), which has already put many users' data security in danger in the past few years. **If you are already using 3X-UI, please switch to the following panels, which are verified to support HTTPS and SSH port forwarding only:**
|
- Web Panel - **WARNING: Please DO NOT USE plain HTTP panels like 3X-UI**, as they are believed to be bribed by Iran GFW for supporting plain HTTP by default and refused to change (https://github.com/XTLS/Xray-core/pull/3884#issuecomment-2439595331), which has already put many users' data security in danger in the past few years. **If you are already using 3X-UI, please switch to the following panels, which are verified to support HTTPS and SSH port forwarding only:**
|
||||||
- [Remnawave](https://github.com/remnawave/panel)
|
|
||||||
- [Marzban](https://github.com/Gozargah/Marzban)
|
- [Marzban](https://github.com/Gozargah/Marzban)
|
||||||
- [Xray-UI](https://github.com/qist/xray-ui)
|
- [Xray-UI](https://github.com/qist/xray-ui)
|
||||||
- [Hiddify](https://github.com/hiddify/Hiddify-Manager)
|
- [Hiddify](https://github.com/hiddify/Hiddify-Manager)
|
||||||
@ -86,7 +85,6 @@
|
|||||||
- [X-flutter](https://github.com/XTLS/X-flutter)
|
- [X-flutter](https://github.com/XTLS/X-flutter)
|
||||||
- [SaeedDev94/Xray](https://github.com/SaeedDev94/Xray)
|
- [SaeedDev94/Xray](https://github.com/SaeedDev94/Xray)
|
||||||
- iOS & macOS arm64
|
- iOS & macOS arm64
|
||||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215)
|
|
||||||
- [FoXray](https://apps.apple.com/app/foxray/id6448898396)
|
- [FoXray](https://apps.apple.com/app/foxray/id6448898396)
|
||||||
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
|
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
|
||||||
- macOS arm64 & x64
|
- macOS arm64 & x64
|
||||||
@ -102,7 +100,6 @@
|
|||||||
|
|
||||||
- iOS & macOS arm64
|
- iOS & macOS arm64
|
||||||
- [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)
|
- [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)
|
||||||
- [Loon](https://apps.apple.com/us/app/loon/id1373567447)
|
|
||||||
- Xray Tools
|
- Xray Tools
|
||||||
- [xray-knife](https://github.com/lilendian0x00/xray-knife)
|
- [xray-knife](https://github.com/lilendian0x00/xray-knife)
|
||||||
- [xray-checker](https://github.com/kutovoys/xray-checker)
|
- [xray-checker](https://github.com/kutovoys/xray-checker)
|
||||||
@ -115,8 +112,9 @@
|
|||||||
- [XrayR](https://github.com/XrayR-project/XrayR)
|
- [XrayR](https://github.com/XrayR-project/XrayR)
|
||||||
- [XrayR-release](https://github.com/XrayR-project/XrayR-release)
|
- [XrayR-release](https://github.com/XrayR-project/XrayR-release)
|
||||||
- [XrayR-V2Board](https://github.com/missuo/XrayR-V2Board)
|
- [XrayR-V2Board](https://github.com/missuo/XrayR-V2Board)
|
||||||
- Cores
|
- [Clash.Meta](https://github.com/MetaCubeX/Clash.Meta)
|
||||||
- [mihomo](https://github.com/MetaCubeX/mihomo)
|
- [clashN](https://github.com/2dust/clashN)
|
||||||
|
- [Clash Meta for Android](https://github.com/MetaCubeX/ClashMetaForAndroid)
|
||||||
- [sing-box](https://github.com/SagerNet/sing-box)
|
- [sing-box](https://github.com/SagerNet/sing-box)
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
@ -33,21 +33,23 @@ type cachedReader struct {
|
|||||||
cache buf.MultiBuffer
|
cache buf.MultiBuffer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *cachedReader) Cache(b *buf.Buffer, deadline time.Duration) error {
|
func (r *cachedReader) Cache(b *buf.Buffer) {
|
||||||
mb, err := r.reader.ReadMultiBufferTimeout(deadline)
|
mb, _ := r.reader.ReadMultiBufferTimeout(time.Millisecond * 100)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Lock()
|
r.Lock()
|
||||||
if !mb.IsEmpty() {
|
if !mb.IsEmpty() {
|
||||||
r.cache, _ = buf.MergeMulti(r.cache, mb)
|
r.cache, _ = buf.MergeMulti(r.cache, mb)
|
||||||
}
|
}
|
||||||
|
cacheLen := r.cache.Len()
|
||||||
|
if cacheLen <= b.Cap() {
|
||||||
b.Clear()
|
b.Clear()
|
||||||
rawBytes := b.Extend(min(r.cache.Len(), b.Cap()))
|
} else {
|
||||||
|
b.Release()
|
||||||
|
*b = *buf.NewWithSize(cacheLen)
|
||||||
|
}
|
||||||
|
rawBytes := b.Extend(cacheLen)
|
||||||
n := r.cache.Copy(rawBytes)
|
n := r.cache.Copy(rawBytes)
|
||||||
b.Resize(0, int32(n))
|
b.Resize(0, int32(n))
|
||||||
r.Unlock()
|
r.Unlock()
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *cachedReader) readInternal() buf.MultiBuffer {
|
func (r *cachedReader) readInternal() buf.MultiBuffer {
|
||||||
@ -96,6 +98,7 @@ type DefaultDispatcher struct {
|
|||||||
router routing.Router
|
router routing.Router
|
||||||
policy policy.Manager
|
policy policy.Manager
|
||||||
stats stats.Manager
|
stats stats.Manager
|
||||||
|
dns dns.Client
|
||||||
fdns dns.FakeDNSEngine
|
fdns dns.FakeDNSEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +109,7 @@ func init() {
|
|||||||
core.OptionalFeatures(ctx, func(fdns dns.FakeDNSEngine) {
|
core.OptionalFeatures(ctx, func(fdns dns.FakeDNSEngine) {
|
||||||
d.fdns = fdns
|
d.fdns = fdns
|
||||||
})
|
})
|
||||||
return d.Init(config.(*Config), om, router, pm, sm)
|
return d.Init(config.(*Config), om, router, pm, sm, dc)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -115,11 +118,12 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Init initializes DefaultDispatcher.
|
// Init initializes DefaultDispatcher.
|
||||||
func (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager) error {
|
func (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager, dns dns.Client) error {
|
||||||
d.ohm = om
|
d.ohm = om
|
||||||
d.router = router
|
d.router = router
|
||||||
d.policy = pm
|
d.policy = pm
|
||||||
d.stats = sm
|
d.stats = sm
|
||||||
|
d.dns = dns
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -351,7 +355,7 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, network net.Network) (SniffResult, error) {
|
func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, network net.Network) (SniffResult, error) {
|
||||||
payload := buf.NewWithSize(32767)
|
payload := buf.New()
|
||||||
defer payload.Release()
|
defer payload.Release()
|
||||||
|
|
||||||
sniffer := NewSniffer(ctx)
|
sniffer := NewSniffer(ctx)
|
||||||
@ -363,36 +367,26 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, netw
|
|||||||
}
|
}
|
||||||
|
|
||||||
contentResult, contentErr := func() (SniffResult, error) {
|
contentResult, contentErr := func() (SniffResult, error) {
|
||||||
cacheDeadline := 200 * time.Millisecond
|
|
||||||
totalAttempt := 0
|
totalAttempt := 0
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
default:
|
default:
|
||||||
cachingStartingTimeStamp := time.Now()
|
totalAttempt++
|
||||||
err := cReader.Cache(payload, cacheDeadline)
|
if totalAttempt > 2 {
|
||||||
if err != nil {
|
return nil, errSniffingTimeout
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
cachingTimeElapsed := time.Since(cachingStartingTimeStamp)
|
|
||||||
cacheDeadline -= cachingTimeElapsed
|
|
||||||
|
|
||||||
|
cReader.Cache(payload)
|
||||||
if !payload.IsEmpty() {
|
if !payload.IsEmpty() {
|
||||||
result, err := sniffer.Sniff(ctx, payload.Bytes(), network)
|
result, err := sniffer.Sniff(ctx, payload.Bytes(), network)
|
||||||
switch err {
|
if err != common.ErrNoClue {
|
||||||
case common.ErrNoClue: // No Clue: protocol not matches, and sniffer cannot determine whether there will be a match or not
|
|
||||||
totalAttempt++
|
|
||||||
case protocol.ErrProtoNeedMoreData: // Protocol Need More Data: protocol matches, but need more data to complete sniffing
|
|
||||||
// in this case, do not add totalAttempt(allow to read until timeout)
|
|
||||||
default:
|
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
totalAttempt++
|
|
||||||
}
|
}
|
||||||
if totalAttempt >= 2 || cacheDeadline <= 0 {
|
if payload.IsFull() {
|
||||||
return nil, errSniffingTimeout
|
return nil, errUnknownContent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -408,6 +402,18 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, netw
|
|||||||
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) {
|
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) {
|
||||||
outbounds := session.OutboundsFromContext(ctx)
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
ob := outbounds[len(outbounds)-1]
|
ob := outbounds[len(outbounds)-1]
|
||||||
|
if hosts, ok := d.dns.(dns.HostsLookup); ok && destination.Address.Family().IsDomain() {
|
||||||
|
proxied := hosts.LookupHosts(ob.Target.String())
|
||||||
|
if proxied != nil {
|
||||||
|
ro := ob.RouteTarget == destination
|
||||||
|
destination.Address = *proxied
|
||||||
|
if ro {
|
||||||
|
ob.RouteTarget = destination
|
||||||
|
} else {
|
||||||
|
ob.Target = destination
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var handler outbound.Handler
|
var handler outbound.Handler
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
|
||||||
"github.com/xtls/xray-core/common/protocol/bittorrent"
|
"github.com/xtls/xray-core/common/protocol/bittorrent"
|
||||||
"github.com/xtls/xray-core/common/protocol/http"
|
"github.com/xtls/xray-core/common/protocol/http"
|
||||||
"github.com/xtls/xray-core/common/protocol/quic"
|
"github.com/xtls/xray-core/common/protocol/quic"
|
||||||
@ -59,17 +58,14 @@ var errUnknownContent = errors.New("unknown content")
|
|||||||
func (s *Sniffer) Sniff(c context.Context, payload []byte, network net.Network) (SniffResult, error) {
|
func (s *Sniffer) Sniff(c context.Context, payload []byte, network net.Network) (SniffResult, error) {
|
||||||
var pendingSniffer []protocolSnifferWithMetadata
|
var pendingSniffer []protocolSnifferWithMetadata
|
||||||
for _, si := range s.sniffer {
|
for _, si := range s.sniffer {
|
||||||
protocolSniffer := si.protocolSniffer
|
s := si.protocolSniffer
|
||||||
if si.metadataSniffer || si.network != network {
|
if si.metadataSniffer || si.network != network {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result, err := protocolSniffer(c, payload)
|
result, err := s(c, payload)
|
||||||
if err == common.ErrNoClue {
|
if err == common.ErrNoClue {
|
||||||
pendingSniffer = append(pendingSniffer, si)
|
pendingSniffer = append(pendingSniffer, si)
|
||||||
continue
|
continue
|
||||||
} else if err == protocol.ErrProtoNeedMoreData { // Sniffer protocol matched, but need more data to complete sniffing
|
|
||||||
s.sniffer = []protocolSnifferWithMetadata{si}
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil && result != nil {
|
if err == nil && result != nil {
|
||||||
|
@ -1,188 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
go_errors "errors"
|
|
||||||
"github.com/xtls/xray-core/common"
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/common/signal/pubsub"
|
|
||||||
"github.com/xtls/xray-core/common/task"
|
|
||||||
dns_feature "github.com/xtls/xray-core/features/dns"
|
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CacheController struct {
|
|
||||||
sync.RWMutex
|
|
||||||
ips map[string]*record
|
|
||||||
pub *pubsub.Service
|
|
||||||
cacheCleanup *task.Periodic
|
|
||||||
name string
|
|
||||||
disableCache bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCacheController(name string, disableCache bool) *CacheController {
|
|
||||||
c := &CacheController{
|
|
||||||
name: name,
|
|
||||||
disableCache: disableCache,
|
|
||||||
ips: make(map[string]*record),
|
|
||||||
pub: pubsub.NewService(),
|
|
||||||
}
|
|
||||||
|
|
||||||
c.cacheCleanup = &task.Periodic{
|
|
||||||
Interval: time.Minute,
|
|
||||||
Execute: c.CacheCleanup,
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// CacheCleanup clears expired items from cache
|
|
||||||
func (c *CacheController) CacheCleanup() error {
|
|
||||||
now := time.Now()
|
|
||||||
c.Lock()
|
|
||||||
defer c.Unlock()
|
|
||||||
|
|
||||||
if len(c.ips) == 0 {
|
|
||||||
return errors.New("nothing to do. stopping...")
|
|
||||||
}
|
|
||||||
|
|
||||||
for domain, record := range c.ips {
|
|
||||||
if record.A != nil && record.A.Expire.Before(now) {
|
|
||||||
record.A = nil
|
|
||||||
}
|
|
||||||
if record.AAAA != nil && record.AAAA.Expire.Before(now) {
|
|
||||||
record.AAAA = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if record.A == nil && record.AAAA == nil {
|
|
||||||
errors.LogDebug(context.Background(), c.name, "cache cleanup ", domain)
|
|
||||||
delete(c.ips, domain)
|
|
||||||
} else {
|
|
||||||
c.ips[domain] = record
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.ips) == 0 {
|
|
||||||
c.ips = make(map[string]*record)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheController) updateIP(req *dnsRequest, ipRec *IPRecord) {
|
|
||||||
elapsed := time.Since(req.start)
|
|
||||||
|
|
||||||
c.Lock()
|
|
||||||
rec, found := c.ips[req.domain]
|
|
||||||
if !found {
|
|
||||||
rec = &record{}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch req.reqType {
|
|
||||||
case dnsmessage.TypeA:
|
|
||||||
rec.A = ipRec
|
|
||||||
case dnsmessage.TypeAAAA:
|
|
||||||
rec.AAAA = ipRec
|
|
||||||
}
|
|
||||||
|
|
||||||
errors.LogInfo(context.Background(), c.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed)
|
|
||||||
c.ips[req.domain] = rec
|
|
||||||
|
|
||||||
switch req.reqType {
|
|
||||||
case dnsmessage.TypeA:
|
|
||||||
c.pub.Publish(req.domain+"4", nil)
|
|
||||||
if !c.disableCache {
|
|
||||||
_, _, err := rec.AAAA.getIPs()
|
|
||||||
if !go_errors.Is(err, errRecordNotFound) {
|
|
||||||
c.pub.Publish(req.domain+"6", nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case dnsmessage.TypeAAAA:
|
|
||||||
c.pub.Publish(req.domain+"6", nil)
|
|
||||||
if !c.disableCache {
|
|
||||||
_, _, err := rec.A.getIPs()
|
|
||||||
if !go_errors.Is(err, errRecordNotFound) {
|
|
||||||
c.pub.Publish(req.domain+"4", nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Unlock()
|
|
||||||
common.Must(c.cacheCleanup.Start())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheController) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
|
|
||||||
c.RLock()
|
|
||||||
record, found := c.ips[domain]
|
|
||||||
c.RUnlock()
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
return nil, 0, errRecordNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
var errs []error
|
|
||||||
var allIPs []net.IP
|
|
||||||
var rTTL uint32 = dns_feature.DefaultTTL
|
|
||||||
|
|
||||||
mergeReq := option.IPv4Enable && option.IPv6Enable
|
|
||||||
|
|
||||||
if option.IPv4Enable {
|
|
||||||
ips, ttl, err := record.A.getIPs()
|
|
||||||
if !mergeReq || go_errors.Is(err, errRecordNotFound) {
|
|
||||||
return ips, ttl, err
|
|
||||||
}
|
|
||||||
if ttl < rTTL {
|
|
||||||
rTTL = ttl
|
|
||||||
}
|
|
||||||
if len(ips) > 0 {
|
|
||||||
allIPs = append(allIPs, ips...)
|
|
||||||
} else {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if option.IPv6Enable {
|
|
||||||
ips, ttl, err := record.AAAA.getIPs()
|
|
||||||
if !mergeReq || go_errors.Is(err, errRecordNotFound) {
|
|
||||||
return ips, ttl, err
|
|
||||||
}
|
|
||||||
if ttl < rTTL {
|
|
||||||
rTTL = ttl
|
|
||||||
}
|
|
||||||
if len(ips) > 0 {
|
|
||||||
allIPs = append(allIPs, ips...)
|
|
||||||
} else {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(allIPs) > 0 {
|
|
||||||
return allIPs, rTTL, nil
|
|
||||||
}
|
|
||||||
if go_errors.Is(errs[0], errs[1]) {
|
|
||||||
return nil, rTTL, errs[0]
|
|
||||||
}
|
|
||||||
return nil, rTTL, errors.Combine(errs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheController) registerSubscribers(domain string, option dns_feature.IPOption) (sub4 *pubsub.Subscriber, sub6 *pubsub.Subscriber) {
|
|
||||||
// ipv4 and ipv6 belong to different subscription groups
|
|
||||||
if option.IPv4Enable {
|
|
||||||
sub4 = c.pub.Subscribe(domain + "4")
|
|
||||||
}
|
|
||||||
if option.IPv6Enable {
|
|
||||||
sub6 = c.pub.Subscribe(domain + "6")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func closeSubscribers(sub4 *pubsub.Subscriber, sub6 *pubsub.Subscriber) {
|
|
||||||
if sub4 != nil {
|
|
||||||
sub4.Close()
|
|
||||||
}
|
|
||||||
if sub6 != nil {
|
|
||||||
sub6.Close()
|
|
||||||
}
|
|
||||||
}
|
|
@ -135,9 +135,6 @@ type NameServer struct {
|
|||||||
Geoip []*router.GeoIP `protobuf:"bytes,3,rep,name=geoip,proto3" json:"geoip,omitempty"`
|
Geoip []*router.GeoIP `protobuf:"bytes,3,rep,name=geoip,proto3" json:"geoip,omitempty"`
|
||||||
OriginalRules []*NameServer_OriginalRule `protobuf:"bytes,4,rep,name=original_rules,json=originalRules,proto3" json:"original_rules,omitempty"`
|
OriginalRules []*NameServer_OriginalRule `protobuf:"bytes,4,rep,name=original_rules,json=originalRules,proto3" json:"original_rules,omitempty"`
|
||||||
QueryStrategy QueryStrategy `protobuf:"varint,7,opt,name=query_strategy,json=queryStrategy,proto3,enum=xray.app.dns.QueryStrategy" json:"query_strategy,omitempty"`
|
QueryStrategy QueryStrategy `protobuf:"varint,7,opt,name=query_strategy,json=queryStrategy,proto3,enum=xray.app.dns.QueryStrategy" json:"query_strategy,omitempty"`
|
||||||
AllowUnexpectedIPs bool `protobuf:"varint,8,opt,name=allowUnexpectedIPs,proto3" json:"allowUnexpectedIPs,omitempty"`
|
|
||||||
Tag string `protobuf:"bytes,9,opt,name=tag,proto3" json:"tag,omitempty"`
|
|
||||||
TimeoutMs uint64 `protobuf:"varint,10,opt,name=timeoutMs,proto3" json:"timeoutMs,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *NameServer) Reset() {
|
func (x *NameServer) Reset() {
|
||||||
@ -219,27 +216,6 @@ func (x *NameServer) GetQueryStrategy() QueryStrategy {
|
|||||||
return QueryStrategy_USE_IP
|
return QueryStrategy_USE_IP
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *NameServer) GetAllowUnexpectedIPs() bool {
|
|
||||||
if x != nil {
|
|
||||||
return x.AllowUnexpectedIPs
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *NameServer) GetTag() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Tag
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *NameServer) GetTimeoutMs() uint64 {
|
|
||||||
if x != nil {
|
|
||||||
return x.TimeoutMs
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
@ -532,7 +508,7 @@ var file_app_dns_config_proto_rawDesc = []byte{
|
|||||||
0x2e, 0x64, 0x6e, 0x73, 0x1a, 0x1c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74,
|
0x2e, 0x64, 0x6e, 0x73, 0x1a, 0x1c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74,
|
||||||
0x2f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f,
|
0x2f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f,
|
||||||
0x74, 0x6f, 0x1a, 0x17, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63,
|
0x74, 0x6f, 0x1a, 0x17, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63,
|
||||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x92, 0x05, 0x0a, 0x0a,
|
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb2, 0x04, 0x0a, 0x0a,
|
||||||
0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x07, 0x61, 0x64,
|
0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x07, 0x61, 0x64,
|
||||||
0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72,
|
0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72,
|
||||||
0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e,
|
0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e,
|
||||||
@ -558,13 +534,7 @@ var file_app_dns_config_proto_rawDesc = []byte{
|
|||||||
0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x78,
|
0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x78,
|
||||||
0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65, 0x72,
|
0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65, 0x72,
|
||||||
0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72, 0x79,
|
0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72, 0x79,
|
||||||
0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x2e, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x6f,
|
0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x1a, 0x5e, 0x0a, 0x0e, 0x50, 0x72, 0x69, 0x6f,
|
||||||
0x77, 0x55, 0x6e, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x49, 0x50, 0x73, 0x18, 0x08,
|
|
||||||
0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x55, 0x6e, 0x65, 0x78, 0x70,
|
|
||||||
0x65, 0x63, 0x74, 0x65, 0x64, 0x49, 0x50, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18,
|
|
||||||
0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69,
|
|
||||||
0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74,
|
|
||||||
0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x1a, 0x5e, 0x0a, 0x0e, 0x50, 0x72, 0x69, 0x6f,
|
|
||||||
0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x04, 0x74, 0x79,
|
0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x04, 0x74, 0x79,
|
||||||
0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
|
0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
|
||||||
0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61,
|
0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61,
|
||||||
|
@ -28,9 +28,6 @@ message NameServer {
|
|||||||
repeated xray.app.router.GeoIP geoip = 3;
|
repeated xray.app.router.GeoIP geoip = 3;
|
||||||
repeated OriginalRule original_rules = 4;
|
repeated OriginalRule original_rules = 4;
|
||||||
QueryStrategy query_strategy = 7;
|
QueryStrategy query_strategy = 7;
|
||||||
bool allowUnexpectedIPs = 8;
|
|
||||||
string tag = 9;
|
|
||||||
uint64 timeoutMs = 10;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DomainMatchingType {
|
enum DomainMatchingType {
|
||||||
|
147
app/dns/dns.go
147
app/dns/dns.go
@ -3,12 +3,11 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
go_errors "errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/app/router"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
@ -20,6 +19,8 @@ import (
|
|||||||
// DNS is a DNS rely server.
|
// DNS is a DNS rely server.
|
||||||
type DNS struct {
|
type DNS struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
|
tag string
|
||||||
|
disableCache bool
|
||||||
disableFallback bool
|
disableFallback bool
|
||||||
disableFallbackIfMatch bool
|
disableFallbackIfMatch bool
|
||||||
ipOption *dns.IPOption
|
ipOption *dns.IPOption
|
||||||
@ -38,6 +39,13 @@ type DomainMatcherInfo struct {
|
|||||||
|
|
||||||
// New creates a new DNS server with given configuration.
|
// New creates a new DNS server with given configuration.
|
||||||
func New(ctx context.Context, config *Config) (*DNS, error) {
|
func New(ctx context.Context, config *Config) (*DNS, error) {
|
||||||
|
var tag string
|
||||||
|
if len(config.Tag) > 0 {
|
||||||
|
tag = config.Tag
|
||||||
|
} else {
|
||||||
|
tag = generateRandomTag()
|
||||||
|
}
|
||||||
|
|
||||||
var clientIP net.IP
|
var clientIP net.IP
|
||||||
switch len(config.ClientIp) {
|
switch len(config.ClientIp) {
|
||||||
case 0, net.IPv4len, net.IPv6len:
|
case 0, net.IPv4len, net.IPv6len:
|
||||||
@ -46,28 +54,26 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
|
|||||||
return nil, errors.New("unexpected client IP length ", len(config.ClientIp))
|
return nil, errors.New("unexpected client IP length ", len(config.ClientIp))
|
||||||
}
|
}
|
||||||
|
|
||||||
var ipOption dns.IPOption
|
var ipOption *dns.IPOption
|
||||||
switch config.QueryStrategy {
|
switch config.QueryStrategy {
|
||||||
case QueryStrategy_USE_IP:
|
case QueryStrategy_USE_IP:
|
||||||
ipOption = dns.IPOption{
|
ipOption = &dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
}
|
}
|
||||||
case QueryStrategy_USE_IP4:
|
case QueryStrategy_USE_IP4:
|
||||||
ipOption = dns.IPOption{
|
ipOption = &dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: false,
|
IPv6Enable: false,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
}
|
}
|
||||||
case QueryStrategy_USE_IP6:
|
case QueryStrategy_USE_IP6:
|
||||||
ipOption = dns.IPOption{
|
ipOption = &dns.IPOption{
|
||||||
IPv4Enable: false,
|
IPv4Enable: false,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
return nil, errors.New("unexpected query strategy ", config.QueryStrategy)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hosts, err := NewStaticHosts(config.StaticHosts)
|
hosts, err := NewStaticHosts(config.StaticHosts)
|
||||||
@ -75,14 +81,8 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
|
|||||||
return nil, errors.New("failed to create hosts").Base(err)
|
return nil, errors.New("failed to create hosts").Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var clients []*Client
|
clients := []*Client{}
|
||||||
domainRuleCount := 0
|
domainRuleCount := 0
|
||||||
|
|
||||||
var defaultTag = config.Tag
|
|
||||||
if len(config.Tag) == 0 {
|
|
||||||
defaultTag = generateRandomTag()
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ns := range config.NameServer {
|
for _, ns := range config.NameServer {
|
||||||
domainRuleCount += len(ns.PrioritizedDomain)
|
domainRuleCount += len(ns.PrioritizedDomain)
|
||||||
}
|
}
|
||||||
@ -90,6 +90,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
|
|||||||
// MatcherInfos is ensured to cover the maximum index domainMatcher could return, where matcher's index starts from 1
|
// MatcherInfos is ensured to cover the maximum index domainMatcher could return, where matcher's index starts from 1
|
||||||
matcherInfos := make([]*DomainMatcherInfo, domainRuleCount+1)
|
matcherInfos := make([]*DomainMatcherInfo, domainRuleCount+1)
|
||||||
domainMatcher := &strmatcher.MatcherGroup{}
|
domainMatcher := &strmatcher.MatcherGroup{}
|
||||||
|
geoipContainer := router.GeoIPMatcherContainer{}
|
||||||
|
|
||||||
for _, ns := range config.NameServer {
|
for _, ns := range config.NameServer {
|
||||||
clientIdx := len(clients)
|
clientIdx := len(clients)
|
||||||
@ -107,18 +108,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
|
|||||||
case net.IPv4len, net.IPv6len:
|
case net.IPv4len, net.IPv6len:
|
||||||
myClientIP = net.IP(ns.ClientIp)
|
myClientIP = net.IP(ns.ClientIp)
|
||||||
}
|
}
|
||||||
|
client, err := NewClient(ctx, ns, myClientIP, geoipContainer, &matcherInfos, updateDomain)
|
||||||
disableCache := config.DisableCache
|
|
||||||
|
|
||||||
var tag = defaultTag
|
|
||||||
if len(ns.Tag) > 0 {
|
|
||||||
tag = ns.Tag
|
|
||||||
}
|
|
||||||
clientIPOption := ResolveIpOptionOverride(ns.QueryStrategy, ipOption)
|
|
||||||
if !clientIPOption.IPv4Enable && !clientIPOption.IPv6Enable {
|
|
||||||
return nil, errors.New("no QueryStrategy available for ", ns.Address)
|
|
||||||
}
|
|
||||||
client, err := NewClient(ctx, ns, myClientIP, disableCache, tag, clientIPOption, &matcherInfos, updateDomain)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to create client").Base(err)
|
return nil, errors.New("failed to create client").Base(err)
|
||||||
}
|
}
|
||||||
@ -127,16 +117,18 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
|
|||||||
|
|
||||||
// If there is no DNS client in config, add a `localhost` DNS client
|
// If there is no DNS client in config, add a `localhost` DNS client
|
||||||
if len(clients) == 0 {
|
if len(clients) == 0 {
|
||||||
clients = append(clients, NewLocalDNSClient(ipOption))
|
clients = append(clients, NewLocalDNSClient())
|
||||||
}
|
}
|
||||||
|
|
||||||
return &DNS{
|
return &DNS{
|
||||||
|
tag: tag,
|
||||||
hosts: hosts,
|
hosts: hosts,
|
||||||
ipOption: &ipOption,
|
ipOption: ipOption,
|
||||||
clients: clients,
|
clients: clients,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
domainMatcher: domainMatcher,
|
domainMatcher: domainMatcher,
|
||||||
matcherInfos: matcherInfos,
|
matcherInfos: matcherInfos,
|
||||||
|
disableCache: config.DisableCache,
|
||||||
disableFallback: config.DisableFallback,
|
disableFallback: config.DisableFallback,
|
||||||
disableFallbackIfMatch: config.DisableFallbackIfMatch,
|
disableFallbackIfMatch: config.DisableFallbackIfMatch,
|
||||||
}, nil
|
}, nil
|
||||||
@ -160,87 +152,94 @@ func (s *DNS) Close() error {
|
|||||||
// IsOwnLink implements proxy.dns.ownLinkVerifier
|
// IsOwnLink implements proxy.dns.ownLinkVerifier
|
||||||
func (s *DNS) IsOwnLink(ctx context.Context) bool {
|
func (s *DNS) IsOwnLink(ctx context.Context) bool {
|
||||||
inbound := session.InboundFromContext(ctx)
|
inbound := session.InboundFromContext(ctx)
|
||||||
if inbound == nil {
|
return inbound != nil && inbound.Tag == s.tag
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, client := range s.clients {
|
|
||||||
if client.tag == inbound.Tag {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupIP implements dns.Client.
|
// LookupIP implements dns.Client.
|
||||||
func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, error) {
|
func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, error) {
|
||||||
// Normalize the FQDN form query
|
|
||||||
domain = strings.TrimSuffix(domain, ".")
|
|
||||||
if domain == "" {
|
if domain == "" {
|
||||||
return nil, 0, errors.New("empty domain name")
|
return nil, errors.New("empty domain name")
|
||||||
}
|
}
|
||||||
|
|
||||||
option.IPv4Enable = option.IPv4Enable && s.ipOption.IPv4Enable
|
option.IPv4Enable = option.IPv4Enable && s.ipOption.IPv4Enable
|
||||||
option.IPv6Enable = option.IPv6Enable && s.ipOption.IPv6Enable
|
option.IPv6Enable = option.IPv6Enable && s.ipOption.IPv6Enable
|
||||||
|
|
||||||
if !option.IPv4Enable && !option.IPv6Enable {
|
if !option.IPv4Enable && !option.IPv6Enable {
|
||||||
return nil, 0, dns.ErrEmptyResponse
|
return nil, dns.ErrEmptyResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalize the FQDN form query
|
||||||
|
domain = strings.TrimSuffix(domain, ".")
|
||||||
|
|
||||||
// Static host lookup
|
// Static host lookup
|
||||||
switch addrs := s.hosts.Lookup(domain, option); {
|
switch addrs := s.hosts.Lookup(domain, option); {
|
||||||
case addrs == nil: // Domain not recorded in static host
|
case addrs == nil: // Domain not recorded in static host
|
||||||
break
|
break
|
||||||
case len(addrs) == 0: // Domain recorded, but no valid IP returned (e.g. IPv4 address with only IPv6 enabled)
|
case len(addrs) == 0: // Domain recorded, but no valid IP returned (e.g. IPv4 address with only IPv6 enabled)
|
||||||
return nil, 0, dns.ErrEmptyResponse
|
return nil, dns.ErrEmptyResponse
|
||||||
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Domain replacement
|
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Domain replacement
|
||||||
errors.LogInfo(s.ctx, "domain replaced: ", domain, " -> ", addrs[0].Domain())
|
errors.LogInfo(s.ctx, "domain replaced: ", domain, " -> ", addrs[0].Domain())
|
||||||
domain = addrs[0].Domain()
|
domain = addrs[0].Domain()
|
||||||
default: // Successfully found ip records in static host
|
default: // Successfully found ip records in static host
|
||||||
errors.LogInfo(s.ctx, "returning ", len(addrs), " IP(s) for domain ", domain, " -> ", addrs)
|
errors.LogInfo(s.ctx, "returning ", len(addrs), " IP(s) for domain ", domain, " -> ", addrs)
|
||||||
ips, err := toNetIP(addrs)
|
return toNetIP(addrs)
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
return ips, 10, nil // Hosts ttl is 10
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name servers lookup
|
// Name servers lookup
|
||||||
var errs []error
|
errs := []error{}
|
||||||
|
ctx := session.ContextWithInbound(s.ctx, &session.Inbound{Tag: s.tag})
|
||||||
for _, client := range s.sortClients(domain) {
|
for _, client := range s.sortClients(domain) {
|
||||||
if !option.FakeEnable && strings.EqualFold(client.Name(), "FakeDNS") {
|
if !option.FakeEnable && strings.EqualFold(client.Name(), "FakeDNS") {
|
||||||
errors.LogDebug(s.ctx, "skip DNS resolution for domain ", domain, " at server ", client.Name())
|
errors.LogDebug(s.ctx, "skip DNS resolution for domain ", domain, " at server ", client.Name())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
ips, err := client.QueryIP(ctx, domain, option, s.disableCache)
|
||||||
ips, ttl, err := client.QueryIP(s.ctx, domain, option)
|
|
||||||
|
|
||||||
if len(ips) > 0 {
|
if len(ips) > 0 {
|
||||||
if ttl == 0 {
|
return ips, nil
|
||||||
ttl = 1
|
|
||||||
}
|
}
|
||||||
return ips, ttl, nil
|
if err != nil {
|
||||||
}
|
|
||||||
|
|
||||||
errors.LogInfoInner(s.ctx, err, "failed to lookup ip for domain ", domain, " at server ", client.Name())
|
errors.LogInfoInner(s.ctx, err, "failed to lookup ip for domain ", domain, " at server ", client.Name())
|
||||||
if err == nil {
|
|
||||||
err = dns.ErrEmptyResponse
|
|
||||||
}
|
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
// 5 for RcodeRefused in miekg/dns, hardcode to reduce binary size
|
||||||
|
if err != context.Canceled && err != context.DeadlineExceeded && err != errExpectedIPNonMatch && err != dns.ErrEmptyResponse && dns.RCodeFromError(err) != 5 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(errs) > 0 {
|
return nil, errors.New("returning nil for domain ", domain).Base(errors.Combine(errs...))
|
||||||
allErrs := errors.Combine(errs...)
|
|
||||||
err0 := errs[0]
|
|
||||||
if errors.AllEqual(err0, allErrs) {
|
|
||||||
if go_errors.Is(err0, dns.ErrEmptyResponse) {
|
|
||||||
return nil, 0, dns.ErrEmptyResponse
|
|
||||||
}
|
}
|
||||||
return nil, 0, errors.New("returning nil for domain ", domain).Base(err0)
|
|
||||||
|
// LookupHosts implements dns.HostsLookup.
|
||||||
|
func (s *DNS) LookupHosts(domain string) *net.Address {
|
||||||
|
domain = strings.TrimSuffix(domain, ".")
|
||||||
|
if domain == "" {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return nil, 0, errors.New("returning nil for domain ", domain).Base(allErrs)
|
// Normalize the FQDN form query
|
||||||
|
addrs := s.hosts.Lookup(domain, *s.ipOption)
|
||||||
|
if len(addrs) > 0 {
|
||||||
|
errors.LogInfo(s.ctx, "domain replaced: ", domain, " -> ", addrs[0].String())
|
||||||
|
return &addrs[0]
|
||||||
}
|
}
|
||||||
return nil, 0, dns.ErrEmptyResponse
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIPOption implements ClientWithIPOption.
|
||||||
|
func (s *DNS) GetIPOption() *dns.IPOption {
|
||||||
|
return s.ipOption
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetQueryOption implements ClientWithIPOption.
|
||||||
|
func (s *DNS) SetQueryOption(isIPv4Enable, isIPv6Enable bool) {
|
||||||
|
s.ipOption.IPv4Enable = isIPv4Enable
|
||||||
|
s.ipOption.IPv6Enable = isIPv6Enable
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFakeDNSOption implements ClientWithIPOption.
|
||||||
|
func (s *DNS) SetFakeDNSOption(isFakeEnable bool) {
|
||||||
|
s.ipOption.FakeEnable = isFakeEnable
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DNS) sortClients(domain string) []*Client {
|
func (s *DNS) sortClients(domain string) []*Client {
|
||||||
@ -251,11 +250,7 @@ func (s *DNS) sortClients(domain string) []*Client {
|
|||||||
|
|
||||||
// Priority domain matching
|
// Priority domain matching
|
||||||
hasMatch := false
|
hasMatch := false
|
||||||
MatchSlice := s.domainMatcher.Match(domain)
|
for _, match := range s.domainMatcher.Match(domain) {
|
||||||
sort.Slice(MatchSlice, func(i, j int) bool {
|
|
||||||
return MatchSlice[i] < MatchSlice[j]
|
|
||||||
})
|
|
||||||
for _, match := range MatchSlice {
|
|
||||||
info := s.matcherInfos[match]
|
info := s.matcherInfos[match]
|
||||||
client := s.clients[info.clientIdx]
|
client := s.clients[info.clientIdx]
|
||||||
domainRule := client.domains[info.domainRuleIdx]
|
domainRule := client.domains[info.domainRuleIdx]
|
||||||
|
@ -76,9 +76,6 @@ func (*staticHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
case q.Name == "notexist.google.com." && q.Qtype == dns.TypeAAAA:
|
case q.Name == "notexist.google.com." && q.Qtype == dns.TypeAAAA:
|
||||||
ans.MsgHdr.Rcode = dns.RcodeNameError
|
ans.MsgHdr.Rcode = dns.RcodeNameError
|
||||||
|
|
||||||
case q.Name == "notexist.google.com." && q.Qtype == dns.TypeA:
|
|
||||||
ans.MsgHdr.Rcode = dns.RcodeNameError
|
|
||||||
|
|
||||||
case q.Name == "hostname." && q.Qtype == dns.TypeA:
|
case q.Name == "hostname." && q.Qtype == dns.TypeA:
|
||||||
rr, _ := dns.NewRR("hostname. IN A 127.0.0.1")
|
rr, _ := dns.NewRR("hostname. IN A 127.0.0.1")
|
||||||
ans.Answer = append(ans.Answer, rr)
|
ans.Answer = append(ans.Answer, rr)
|
||||||
@ -120,6 +117,7 @@ func TestUDPServerSubnet(t *testing.T) {
|
|||||||
Handler: &staticHandler{},
|
Handler: &staticHandler{},
|
||||||
UDPSize: 1200,
|
UDPSize: 1200,
|
||||||
}
|
}
|
||||||
|
|
||||||
go dnsServer.ListenAndServe()
|
go dnsServer.ListenAndServe()
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
@ -157,7 +155,7 @@ func TestUDPServerSubnet(t *testing.T) {
|
|||||||
|
|
||||||
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
|
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
|
||||||
|
|
||||||
ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("google.com", feature_dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -218,7 +216,7 @@ func TestUDPServer(t *testing.T) {
|
|||||||
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
|
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
|
||||||
|
|
||||||
{
|
{
|
||||||
ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("google.com", feature_dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -233,7 +231,7 @@ func TestUDPServer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
ips, _, err := client.LookupIP("facebook.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("facebook.com", feature_dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -248,7 +246,7 @@ func TestUDPServer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
_, _, err := client.LookupIP("notexist.google.com", feature_dns.IPOption{
|
_, err := client.LookupIP("notexist.google.com", feature_dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -262,7 +260,7 @@ func TestUDPServer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
ips, _, err := client.LookupIP("ipv4only.google.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("ipv4only.google.com", feature_dns.IPOption{
|
||||||
IPv4Enable: false,
|
IPv4Enable: false,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -278,7 +276,7 @@ func TestUDPServer(t *testing.T) {
|
|||||||
dnsServer.Shutdown()
|
dnsServer.Shutdown()
|
||||||
|
|
||||||
{
|
{
|
||||||
ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("google.com", feature_dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -359,7 +357,7 @@ func TestPrioritizedDomain(t *testing.T) {
|
|||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
{
|
{
|
||||||
ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("google.com", feature_dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -425,7 +423,7 @@ func TestUDPServerIPv6(t *testing.T) {
|
|||||||
|
|
||||||
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
|
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
|
||||||
{
|
{
|
||||||
ips, _, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{
|
||||||
IPv4Enable: false,
|
IPv4Enable: false,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -494,7 +492,7 @@ func TestStaticHostDomain(t *testing.T) {
|
|||||||
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
|
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
|
||||||
|
|
||||||
{
|
{
|
||||||
ips, _, err := client.LookupIP("example.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("example.com", feature_dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -605,7 +603,7 @@ func TestIPMatch(t *testing.T) {
|
|||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
{
|
{
|
||||||
ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("google.com", feature_dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -728,7 +726,7 @@ func TestLocalDomain(t *testing.T) {
|
|||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
{ // Will match dotless:
|
{ // Will match dotless:
|
||||||
ips, _, err := client.LookupIP("hostname", feature_dns.IPOption{
|
ips, err := client.LookupIP("hostname", feature_dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -743,7 +741,7 @@ func TestLocalDomain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{ // Will match domain:local
|
{ // Will match domain:local
|
||||||
ips, _, err := client.LookupIP("hostname.local", feature_dns.IPOption{
|
ips, err := client.LookupIP("hostname.local", feature_dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -758,7 +756,7 @@ func TestLocalDomain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{ // Will match static ip
|
{ // Will match static ip
|
||||||
ips, _, err := client.LookupIP("hostnamestatic", feature_dns.IPOption{
|
ips, err := client.LookupIP("hostnamestatic", feature_dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -773,7 +771,7 @@ func TestLocalDomain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{ // Will match domain replacing
|
{ // Will match domain replacing
|
||||||
ips, _, err := client.LookupIP("hostnamealias", feature_dns.IPOption{
|
ips, err := client.LookupIP("hostnamealias", feature_dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -787,8 +785,8 @@ func TestLocalDomain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // Will match dotless:localhost, but not expectedIPs: 127.0.0.2, 127.0.0.3, then matches at dotless:
|
{ // Will match dotless:localhost, but not expectIPs: 127.0.0.2, 127.0.0.3, then matches at dotless:
|
||||||
ips, _, err := client.LookupIP("localhost", feature_dns.IPOption{
|
ips, err := client.LookupIP("localhost", feature_dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -802,8 +800,8 @@ func TestLocalDomain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // Will match dotless:localhost, and expectedIPs: 127.0.0.2, 127.0.0.3
|
{ // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3
|
||||||
ips, _, err := client.LookupIP("localhost-a", feature_dns.IPOption{
|
ips, err := client.LookupIP("localhost-a", feature_dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -817,8 +815,8 @@ func TestLocalDomain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // Will match dotless:localhost, and expectedIPs: 127.0.0.2, 127.0.0.3
|
{ // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3
|
||||||
ips, _, err := client.LookupIP("localhost-b", feature_dns.IPOption{
|
ips, err := client.LookupIP("localhost-b", feature_dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -833,7 +831,7 @@ func TestLocalDomain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{ // Will match dotless:
|
{ // Will match dotless:
|
||||||
ips, _, err := client.LookupIP("Mijia Cloud", feature_dns.IPOption{
|
ips, err := client.LookupIP("Mijia Cloud", feature_dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -999,7 +997,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
|||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
{ // Will match server 1,2 and server 1 returns expected ip
|
{ // Will match server 1,2 and server 1 returns expected ip
|
||||||
ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("google.com", feature_dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -1014,7 +1012,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{ // Will match server 1,2 and server 1 returns unexpected ip, then server 2 returns expected one
|
{ // Will match server 1,2 and server 1 returns unexpected ip, then server 2 returns expected one
|
||||||
ips, _, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: false,
|
IPv6Enable: false,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -1029,7 +1027,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{ // Will match server 3,1,2 and server 3 returns expected one
|
{ // Will match server 3,1,2 and server 3 returns expected one
|
||||||
ips, _, err := client.LookupIP("api.google.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("api.google.com", feature_dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
@ -1044,7 +1042,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{ // Will match server 4,3,1,2 and server 4 returns expected one
|
{ // Will match server 4,3,1,2 and server 4 returns expected one
|
||||||
ips, _, err := client.LookupIP("v2.api.google.com", feature_dns.IPOption{
|
ips, err := client.LookupIP("v2.api.google.com", feature_dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
|
@ -32,30 +32,29 @@ type record struct {
|
|||||||
// IPRecord is a cacheable item for a resolved domain
|
// IPRecord is a cacheable item for a resolved domain
|
||||||
type IPRecord struct {
|
type IPRecord struct {
|
||||||
ReqID uint16
|
ReqID uint16
|
||||||
IP []net.IP
|
IP []net.Address
|
||||||
Expire time.Time
|
Expire time.Time
|
||||||
RCode dnsmessage.RCode
|
RCode dnsmessage.RCode
|
||||||
RawHeader *dnsmessage.Header
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *IPRecord) getIPs() ([]net.IP, uint32, error) {
|
func (r *IPRecord) getIPs() ([]net.Address, error) {
|
||||||
if r == nil {
|
if r == nil || r.Expire.Before(time.Now()) {
|
||||||
return nil, 0, errRecordNotFound
|
return nil, errRecordNotFound
|
||||||
}
|
}
|
||||||
untilExpire := time.Until(r.Expire)
|
|
||||||
if untilExpire <= 0 {
|
|
||||||
return nil, 0, errRecordNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
ttl := uint32(untilExpire/time.Second) + uint32(1)
|
|
||||||
if r.RCode != dnsmessage.RCodeSuccess {
|
if r.RCode != dnsmessage.RCodeSuccess {
|
||||||
return nil, ttl, dns_feature.RCodeError(r.RCode)
|
return nil, dns_feature.RCodeError(r.RCode)
|
||||||
}
|
}
|
||||||
if len(r.IP) == 0 {
|
return r.IP, nil
|
||||||
return nil, ttl, dns_feature.ErrEmptyResponse
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.IP, ttl, nil
|
func isNewer(baseRec *IPRecord, newRec *IPRecord) bool {
|
||||||
|
if newRec == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if baseRec == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return baseRec.Expire.Before(newRec.Expire)
|
||||||
}
|
}
|
||||||
|
|
||||||
var errRecordNotFound = errors.New("record not found")
|
var errRecordNotFound = errors.New("record not found")
|
||||||
@ -68,20 +67,11 @@ type dnsRequest struct {
|
|||||||
msg *dnsmessage.Message
|
msg *dnsmessage.Message
|
||||||
}
|
}
|
||||||
|
|
||||||
func genEDNS0Options(clientIP net.IP, padding int) *dnsmessage.Resource {
|
func genEDNS0Options(clientIP net.IP) *dnsmessage.Resource {
|
||||||
if len(clientIP) == 0 && padding == 0 {
|
if len(clientIP) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const EDNS0SUBNET = 0x8
|
|
||||||
const EDNS0PADDING = 0xc
|
|
||||||
|
|
||||||
opt := new(dnsmessage.Resource)
|
|
||||||
common.Must(opt.Header.SetEDNS0(1350, 0xfe00, true))
|
|
||||||
body := dnsmessage.OPTResource{}
|
|
||||||
opt.Body = &body
|
|
||||||
|
|
||||||
if len(clientIP) != 0 {
|
|
||||||
var netmask int
|
var netmask int
|
||||||
var family uint16
|
var family uint16
|
||||||
|
|
||||||
@ -108,19 +98,18 @@ func genEDNS0Options(clientIP net.IP, padding int) *dnsmessage.Resource {
|
|||||||
b = append(b, ip[:needLength]...)
|
b = append(b, ip[:needLength]...)
|
||||||
}
|
}
|
||||||
|
|
||||||
body.Options = append(body.Options,
|
const EDNS0SUBNET = 0x08
|
||||||
dnsmessage.Option{
|
|
||||||
|
opt := new(dnsmessage.Resource)
|
||||||
|
common.Must(opt.Header.SetEDNS0(1350, 0xfe00, true))
|
||||||
|
|
||||||
|
opt.Body = &dnsmessage.OPTResource{
|
||||||
|
Options: []dnsmessage.Option{
|
||||||
|
{
|
||||||
Code: EDNS0SUBNET,
|
Code: EDNS0SUBNET,
|
||||||
Data: b,
|
Data: b,
|
||||||
})
|
},
|
||||||
}
|
},
|
||||||
|
|
||||||
if padding != 0 {
|
|
||||||
body.Options = append(body.Options,
|
|
||||||
dnsmessage.Option{
|
|
||||||
Code: EDNS0PADDING,
|
|
||||||
Data: make([]byte, padding),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return opt
|
return opt
|
||||||
@ -192,8 +181,7 @@ func parseResponse(payload []byte) (*IPRecord, error) {
|
|||||||
ipRecord := &IPRecord{
|
ipRecord := &IPRecord{
|
||||||
ReqID: h.ID,
|
ReqID: h.ID,
|
||||||
RCode: h.RCode,
|
RCode: h.RCode,
|
||||||
Expire: now.Add(time.Second * dns_feature.DefaultTTL),
|
Expire: now.Add(time.Second * 600),
|
||||||
RawHeader: &h,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
L:
|
L:
|
||||||
@ -208,7 +196,7 @@ L:
|
|||||||
|
|
||||||
ttl := ah.TTL
|
ttl := ah.TTL
|
||||||
if ttl == 0 {
|
if ttl == 0 {
|
||||||
ttl = 1
|
ttl = 600
|
||||||
}
|
}
|
||||||
expire := now.Add(time.Duration(ttl) * time.Second)
|
expire := now.Add(time.Duration(ttl) * time.Second)
|
||||||
if ipRecord.Expire.After(expire) {
|
if ipRecord.Expire.After(expire) {
|
||||||
@ -222,17 +210,14 @@ L:
|
|||||||
errors.LogInfoInner(context.Background(), err, "failed to parse A record for domain: ", ah.Name)
|
errors.LogInfoInner(context.Background(), err, "failed to parse A record for domain: ", ah.Name)
|
||||||
break L
|
break L
|
||||||
}
|
}
|
||||||
ipRecord.IP = append(ipRecord.IP, net.IPAddress(ans.A[:]).IP())
|
ipRecord.IP = append(ipRecord.IP, net.IPAddress(ans.A[:]))
|
||||||
case dnsmessage.TypeAAAA:
|
case dnsmessage.TypeAAAA:
|
||||||
ans, err := parser.AAAAResource()
|
ans, err := parser.AAAAResource()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogInfoInner(context.Background(), err, "failed to parse AAAA record for domain: ", ah.Name)
|
errors.LogInfoInner(context.Background(), err, "failed to parse AAAA record for domain: ", ah.Name)
|
||||||
break L
|
break L
|
||||||
}
|
}
|
||||||
newIP := net.IPAddress(ans.AAAA[:]).IP()
|
ipRecord.IP = append(ipRecord.IP, net.IPAddress(ans.AAAA[:]))
|
||||||
if len(newIP) == net.IPv6len {
|
|
||||||
ipRecord.IP = append(ipRecord.IP, newIP)
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
if err := parser.SkipAnswer(); err != nil {
|
if err := parser.SkipAnswer(); err != nil {
|
||||||
errors.LogInfoInner(context.Background(), err, "failed to skip answer")
|
errors.LogInfoInner(context.Background(), err, "failed to skip answer")
|
||||||
|
@ -51,7 +51,7 @@ func Test_parseResponse(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"empty",
|
"empty",
|
||||||
&IPRecord{0, []net.IP(nil), time.Time{}, dnsmessage.RCodeSuccess, nil},
|
&IPRecord{0, []net.Address(nil), time.Time{}, dnsmessage.RCodeSuccess},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -63,16 +63,15 @@ func Test_parseResponse(t *testing.T) {
|
|||||||
"a record",
|
"a record",
|
||||||
&IPRecord{
|
&IPRecord{
|
||||||
1,
|
1,
|
||||||
[]net.IP{net.ParseIP("8.8.8.8"), net.ParseIP("8.8.4.4")},
|
[]net.Address{net.ParseAddress("8.8.8.8"), net.ParseAddress("8.8.4.4")},
|
||||||
time.Time{},
|
time.Time{},
|
||||||
dnsmessage.RCodeSuccess,
|
dnsmessage.RCodeSuccess,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"aaaa record",
|
"aaaa record",
|
||||||
&IPRecord{2, []net.IP{net.ParseIP("2001::123:8888"), net.ParseIP("2001::123:8844")}, time.Time{}, dnsmessage.RCodeSuccess, nil},
|
&IPRecord{2, []net.Address{net.ParseAddress("2001::123:8888"), net.ParseAddress("2001::123:8844")}, time.Time{}, dnsmessage.RCodeSuccess},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -85,9 +84,8 @@ func Test_parseResponse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if got != nil {
|
if got != nil {
|
||||||
// reset the time and RawHeader
|
// reset the time
|
||||||
got.Expire = time.Time{}
|
got.Expire = time.Time{}
|
||||||
got.RawHeader = nil
|
|
||||||
}
|
}
|
||||||
if cmp.Diff(got, tt.want) != "" {
|
if cmp.Diff(got, tt.want) != "" {
|
||||||
t.Error(cmp.Diff(got, tt.want))
|
t.Error(cmp.Diff(got, tt.want))
|
||||||
@ -156,7 +154,7 @@ func Test_genEDNS0Options(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if got := genEDNS0Options(tt.args.clientIP, 0); got == nil {
|
if got := genEDNS0Options(tt.args.clientIP); got == nil {
|
||||||
t.Errorf("genEDNS0Options() = %v, want %v", got, tt.want)
|
t.Errorf("genEDNS0Options() = %v, want %v", got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -2,6 +2,7 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/strmatcher"
|
"github.com/xtls/xray-core/common/strmatcher"
|
||||||
@ -40,6 +41,8 @@ func NewStaticHosts(hosts []*Config_HostMapping) (*StaticHosts, error) {
|
|||||||
}
|
}
|
||||||
ips = append(ips, addr)
|
ips = append(ips, addr)
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
return nil, errors.New("neither IP address nor proxied domain specified for domain: ", mapping.Domain).AtWarning()
|
||||||
}
|
}
|
||||||
|
|
||||||
sh.ips[id] = ips
|
sh.ips[id] = ips
|
||||||
@ -59,14 +62,9 @@ func filterIP(ips []net.Address, option dns.IPOption) []net.Address {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *StaticHosts) lookupInternal(domain string) []net.Address {
|
func (h *StaticHosts) lookupInternal(domain string) []net.Address {
|
||||||
ips := make([]net.Address, 0)
|
var ips []net.Address
|
||||||
found := false
|
|
||||||
for _, id := range h.matchers.Match(domain) {
|
for _, id := range h.matchers.Match(domain) {
|
||||||
ips = append(ips, h.ips[id]...)
|
ips = append(ips, h.ips[id]...)
|
||||||
found = true
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
return ips
|
return ips
|
||||||
}
|
}
|
||||||
@ -74,7 +72,7 @@ func (h *StaticHosts) lookupInternal(domain string) []net.Address {
|
|||||||
func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) []net.Address {
|
func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) []net.Address {
|
||||||
switch addrs := h.lookupInternal(domain); {
|
switch addrs := h.lookupInternal(domain); {
|
||||||
case len(addrs) == 0: // Not recorded in static hosts, return nil
|
case len(addrs) == 0: // Not recorded in static hosts, return nil
|
||||||
return addrs
|
return nil
|
||||||
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Try to unwrap domain
|
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Try to unwrap domain
|
||||||
errors.LogDebug(context.Background(), "found replaced domain: ", domain, " -> ", addrs[0].Domain(), ". Try to unwrap it")
|
errors.LogDebug(context.Background(), "found replaced domain: ", domain, " -> ", addrs[0].Domain(), ". Try to unwrap it")
|
||||||
if maxDepth > 0 {
|
if maxDepth > 0 {
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"github.com/xtls/xray-core/app/router"
|
"github.com/xtls/xray-core/app/router"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/session"
|
|
||||||
"github.com/xtls/xray-core/common/strmatcher"
|
"github.com/xtls/xray-core/common/strmatcher"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/features/dns"
|
"github.com/xtls/xray-core/features/dns"
|
||||||
@ -21,23 +20,22 @@ type Server interface {
|
|||||||
// Name of the Client.
|
// Name of the Client.
|
||||||
Name() string
|
Name() string
|
||||||
// QueryIP sends IP queries to its configured server.
|
// QueryIP sends IP queries to its configured server.
|
||||||
QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error)
|
QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns.IPOption, disableCache bool) ([]net.IP, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client is the interface for DNS client.
|
// Client is the interface for DNS client.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
server Server
|
server Server
|
||||||
|
clientIP net.IP
|
||||||
skipFallback bool
|
skipFallback bool
|
||||||
domains []string
|
domains []string
|
||||||
expectedIPs []*router.GeoIPMatcher
|
expectIPs []*router.GeoIPMatcher
|
||||||
allowUnexpectedIPs bool
|
|
||||||
tag string
|
|
||||||
timeoutMs time.Duration
|
|
||||||
ipOption *dns.IPOption
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errExpectedIPNonMatch = errors.New("expectIPs not match")
|
||||||
|
|
||||||
// NewServer creates a name server object according to the network destination url.
|
// NewServer creates a name server object according to the network destination url.
|
||||||
func NewServer(ctx context.Context, dest net.Destination, dispatcher routing.Dispatcher, disableCache bool, clientIP net.IP) (Server, error) {
|
func NewServer(ctx context.Context, dest net.Destination, dispatcher routing.Dispatcher, queryStrategy QueryStrategy) (Server, error) {
|
||||||
if address := dest.Address; address.Family().IsDomain() {
|
if address := dest.Address; address.Family().IsDomain() {
|
||||||
u, err := url.Parse(address.Domain())
|
u, err := url.Parse(address.Domain())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -45,29 +43,24 @@ func NewServer(ctx context.Context, dest net.Destination, dispatcher routing.Dis
|
|||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case strings.EqualFold(u.String(), "localhost"):
|
case strings.EqualFold(u.String(), "localhost"):
|
||||||
return NewLocalNameServer(), nil
|
return NewLocalNameServer(queryStrategy), nil
|
||||||
case strings.EqualFold(u.Scheme, "https"): // DNS-over-HTTPS Remote mode
|
case strings.EqualFold(u.Scheme, "https"): // DNS-over-HTTPS Remote mode
|
||||||
return NewDoHNameServer(u, dispatcher, false, disableCache, clientIP), nil
|
return NewDoHNameServer(u, dispatcher, queryStrategy, false)
|
||||||
case strings.EqualFold(u.Scheme, "h2c"): // DNS-over-HTTPS h2c Remote mode
|
case strings.EqualFold(u.Scheme, "h2c"): // DNS-over-HTTPS h2c Remote mode
|
||||||
return NewDoHNameServer(u, dispatcher, true, disableCache, clientIP), nil
|
return NewDoHNameServer(u, dispatcher, queryStrategy, true)
|
||||||
case strings.EqualFold(u.Scheme, "https+local"): // DNS-over-HTTPS Local mode
|
case strings.EqualFold(u.Scheme, "https+local"): // DNS-over-HTTPS Local mode
|
||||||
return NewDoHNameServer(u, nil, false, disableCache, clientIP), nil
|
return NewDoHLocalNameServer(u, queryStrategy), nil
|
||||||
case strings.EqualFold(u.Scheme, "h2c+local"): // DNS-over-HTTPS h2c Local mode
|
|
||||||
return NewDoHNameServer(u, nil, true, disableCache, clientIP), nil
|
|
||||||
case strings.EqualFold(u.Scheme, "quic+local"): // DNS-over-QUIC Local mode
|
case strings.EqualFold(u.Scheme, "quic+local"): // DNS-over-QUIC Local mode
|
||||||
return NewQUICNameServer(u, disableCache, clientIP)
|
return NewQUICNameServer(u, queryStrategy)
|
||||||
case strings.EqualFold(u.Scheme, "tcp"): // DNS-over-TCP Remote mode
|
case strings.EqualFold(u.Scheme, "tcp"): // DNS-over-TCP Remote mode
|
||||||
return NewTCPNameServer(u, dispatcher, disableCache, clientIP)
|
return NewTCPNameServer(u, dispatcher, queryStrategy)
|
||||||
case strings.EqualFold(u.Scheme, "tcp+local"): // DNS-over-TCP Local mode
|
case strings.EqualFold(u.Scheme, "tcp+local"): // DNS-over-TCP Local mode
|
||||||
return NewTCPLocalNameServer(u, disableCache, clientIP)
|
return NewTCPLocalNameServer(u, queryStrategy)
|
||||||
case strings.EqualFold(u.String(), "fakedns"):
|
case strings.EqualFold(u.String(), "fakedns"):
|
||||||
var fd dns.FakeDNSEngine
|
var fd dns.FakeDNSEngine
|
||||||
err = core.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) {
|
core.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) {
|
||||||
fd = fdns
|
fd = fdns
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return NewFakeDNSServer(fd), nil
|
return NewFakeDNSServer(fd), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,7 +68,7 @@ func NewServer(ctx context.Context, dest net.Destination, dispatcher routing.Dis
|
|||||||
dest.Network = net.Network_UDP
|
dest.Network = net.Network_UDP
|
||||||
}
|
}
|
||||||
if dest.Network == net.Network_UDP { // UDP classic DNS mode
|
if dest.Network == net.Network_UDP { // UDP classic DNS mode
|
||||||
return NewClassicNameServer(dest, dispatcher, disableCache, clientIP), nil
|
return NewClassicNameServer(dest, dispatcher, queryStrategy), nil
|
||||||
}
|
}
|
||||||
return nil, errors.New("No available name server could be created from ", dest).AtWarning()
|
return nil, errors.New("No available name server could be created from ", dest).AtWarning()
|
||||||
}
|
}
|
||||||
@ -85,9 +78,7 @@ func NewClient(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
ns *NameServer,
|
ns *NameServer,
|
||||||
clientIP net.IP,
|
clientIP net.IP,
|
||||||
disableCache bool,
|
container router.GeoIPMatcherContainer,
|
||||||
tag string,
|
|
||||||
ipOption dns.IPOption,
|
|
||||||
matcherInfos *[]*DomainMatcherInfo,
|
matcherInfos *[]*DomainMatcherInfo,
|
||||||
updateDomainRule func(strmatcher.Matcher, int, []*DomainMatcherInfo) error,
|
updateDomainRule func(strmatcher.Matcher, int, []*DomainMatcherInfo) error,
|
||||||
) (*Client, error) {
|
) (*Client, error) {
|
||||||
@ -95,7 +86,7 @@ func NewClient(
|
|||||||
|
|
||||||
err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {
|
err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {
|
||||||
// Create a new server for each client for now
|
// Create a new server for each client for now
|
||||||
server, err := NewServer(ctx, ns.Address.AsDestination(), dispatcher, disableCache, clientIP)
|
server, err := NewServer(ctx, ns.Address.AsDestination(), dispatcher, ns.GetQueryStrategy())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("failed to create nameserver").Base(err).AtWarning()
|
return errors.New("failed to create nameserver").Base(err).AtWarning()
|
||||||
}
|
}
|
||||||
@ -152,7 +143,7 @@ func NewClient(
|
|||||||
// Establish expected IPs
|
// Establish expected IPs
|
||||||
var matchers []*router.GeoIPMatcher
|
var matchers []*router.GeoIPMatcher
|
||||||
for _, geoip := range ns.Geoip {
|
for _, geoip := range ns.Geoip {
|
||||||
matcher, err := router.GlobalGeoIPContainer.Add(geoip)
|
matcher, err := container.Add(geoip)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("failed to create ip matcher").Base(err).AtWarning()
|
return errors.New("failed to create ip matcher").Base(err).AtWarning()
|
||||||
}
|
}
|
||||||
@ -164,23 +155,15 @@ func NewClient(
|
|||||||
case *net.IPOrDomain_Domain:
|
case *net.IPOrDomain_Domain:
|
||||||
errors.LogInfo(ctx, "DNS: client ", ns.Address.Address.GetDomain(), " uses clientIP ", clientIP.String())
|
errors.LogInfo(ctx, "DNS: client ", ns.Address.Address.GetDomain(), " uses clientIP ", clientIP.String())
|
||||||
case *net.IPOrDomain_Ip:
|
case *net.IPOrDomain_Ip:
|
||||||
errors.LogInfo(ctx, "DNS: client ", net.IP(ns.Address.Address.GetIp()), " uses clientIP ", clientIP.String())
|
errors.LogInfo(ctx, "DNS: client ", ns.Address.Address.GetIp(), " uses clientIP ", clientIP.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var timeoutMs = 4000 * time.Millisecond
|
|
||||||
if ns.TimeoutMs > 0 {
|
|
||||||
timeoutMs = time.Duration(ns.TimeoutMs) * time.Millisecond
|
|
||||||
}
|
|
||||||
|
|
||||||
client.server = server
|
client.server = server
|
||||||
|
client.clientIP = clientIP
|
||||||
client.skipFallback = ns.SkipFallback
|
client.skipFallback = ns.SkipFallback
|
||||||
client.domains = rules
|
client.domains = rules
|
||||||
client.expectedIPs = matchers
|
client.expectIPs = matchers
|
||||||
client.allowUnexpectedIPs = ns.AllowUnexpectedIPs
|
|
||||||
client.tag = tag
|
|
||||||
client.timeoutMs = timeoutMs
|
|
||||||
client.ipOption = &ipOption
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
return client, err
|
return client, err
|
||||||
@ -192,53 +175,36 @@ func (c *Client) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// QueryIP sends DNS query to the name server with the client's IP.
|
// QueryIP sends DNS query to the name server with the client's IP.
|
||||||
func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error) {
|
func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption, disableCache bool) ([]net.IP, error) {
|
||||||
option.IPv4Enable = option.IPv4Enable && c.ipOption.IPv4Enable
|
ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
|
||||||
option.IPv6Enable = option.IPv6Enable && c.ipOption.IPv6Enable
|
ips, err := c.server.QueryIP(ctx, domain, c.clientIP, option, disableCache)
|
||||||
if !option.IPv4Enable && !option.IPv6Enable {
|
|
||||||
return nil, 0, dns.ErrEmptyResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, c.timeoutMs)
|
|
||||||
ctx = session.ContextWithInbound(ctx, &session.Inbound{Tag: c.tag})
|
|
||||||
ips, ttl, err := c.server.QueryIP(ctx, domain, option)
|
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return ips, err
|
||||||
}
|
}
|
||||||
|
return c.MatchExpectedIPs(domain, ips)
|
||||||
if len(ips) == 0 {
|
|
||||||
return nil, 0, dns.ErrEmptyResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.expectedIPs) > 0 {
|
|
||||||
newIps := c.MatchExpectedIPs(domain, ips)
|
|
||||||
if len(newIps) == 0 {
|
|
||||||
if !c.allowUnexpectedIPs {
|
|
||||||
return nil, 0, dns.ErrEmptyResponse
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ips = newIps
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ips, ttl, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchExpectedIPs matches queried domain IPs with expected IPs and returns matched ones.
|
// MatchExpectedIPs matches queried domain IPs with expected IPs and returns matched ones.
|
||||||
func (c *Client) MatchExpectedIPs(domain string, ips []net.IP) []net.IP {
|
func (c *Client) MatchExpectedIPs(domain string, ips []net.IP) ([]net.IP, error) {
|
||||||
var newIps []net.IP
|
if len(c.expectIPs) == 0 {
|
||||||
|
return ips, nil
|
||||||
|
}
|
||||||
|
newIps := []net.IP{}
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
for _, matcher := range c.expectedIPs {
|
for _, matcher := range c.expectIPs {
|
||||||
if matcher.Match(ip) {
|
if matcher.Match(ip) {
|
||||||
newIps = append(newIps, ip)
|
newIps = append(newIps, ip)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
errors.LogDebug(context.Background(), "domain ", domain, " expectedIPs ", newIps, " matched at server ", c.Name())
|
if len(newIps) == 0 {
|
||||||
return newIps
|
return nil, errExpectedIPNonMatch
|
||||||
|
}
|
||||||
|
errors.LogDebug(context.Background(), "domain ", domain, " expectIPs ", newIps, " matched at server ", c.Name())
|
||||||
|
return newIps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ResolveIpOptionOverride(queryStrategy QueryStrategy, ipOption dns.IPOption) dns.IPOption {
|
func ResolveIpOptionOverride(queryStrategy QueryStrategy, ipOption dns.IPOption) dns.IPOption {
|
||||||
|
@ -4,26 +4,26 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
go_errors "errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
utls "github.com/refraction-networking/utls"
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/crypto"
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/log"
|
"github.com/xtls/xray-core/common/log"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/net/cnc"
|
"github.com/xtls/xray-core/common/net/cnc"
|
||||||
"github.com/xtls/xray-core/common/protocol/dns"
|
"github.com/xtls/xray-core/common/protocol/dns"
|
||||||
"github.com/xtls/xray-core/common/session"
|
"github.com/xtls/xray-core/common/session"
|
||||||
|
"github.com/xtls/xray-core/common/signal/pubsub"
|
||||||
|
"github.com/xtls/xray-core/common/task"
|
||||||
dns_feature "github.com/xtls/xray-core/features/dns"
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
"github.com/xtls/xray-core/features/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,50 +31,45 @@ import (
|
|||||||
// which is compatible with traditional dns over udp(RFC1035),
|
// which is compatible with traditional dns over udp(RFC1035),
|
||||||
// thus most of the DOH implementation is copied from udpns.go
|
// thus most of the DOH implementation is copied from udpns.go
|
||||||
type DoHNameServer struct {
|
type DoHNameServer struct {
|
||||||
cacheController *CacheController
|
dispatcher routing.Dispatcher
|
||||||
|
sync.RWMutex
|
||||||
|
ips map[string]*record
|
||||||
|
pub *pubsub.Service
|
||||||
|
cleanup *task.Periodic
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
dohURL string
|
dohURL string
|
||||||
clientIP net.IP
|
name string
|
||||||
|
queryStrategy QueryStrategy
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDoHNameServer creates DOH/DOHL client object for remote/local resolving.
|
// NewDoHNameServer creates DOH server object for remote resolving.
|
||||||
func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, h2c bool, disableCache bool, clientIP net.IP) *DoHNameServer {
|
func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, queryStrategy QueryStrategy, h2c bool) (*DoHNameServer, error) {
|
||||||
url.Scheme = "https"
|
url.Scheme = "https"
|
||||||
mode := "DOH"
|
errors.LogInfo(context.Background(), "DNS: created Remote DNS-over-HTTPS client for ", url.String(), ", with h2c ", h2c)
|
||||||
if dispatcher == nil {
|
s := baseDOHNameServer(url, "DOH", queryStrategy)
|
||||||
mode = "DOHL"
|
|
||||||
}
|
s.dispatcher = dispatcher
|
||||||
errors.LogInfo(context.Background(), "DNS: created ", mode, " client for ", url.String(), ", with h2c ", h2c)
|
dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
s := &DoHNameServer{
|
|
||||||
cacheController: NewCacheController(mode+"//"+url.Host, disableCache),
|
|
||||||
dohURL: url.String(),
|
|
||||||
clientIP: clientIP,
|
|
||||||
}
|
|
||||||
s.httpClient = &http.Client{
|
|
||||||
Transport: &http2.Transport{
|
|
||||||
IdleConnTimeout: net.ConnIdleTimeout,
|
|
||||||
ReadIdleTimeout: net.ChromeH2KeepAlivePeriod,
|
|
||||||
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
|
|
||||||
dest, err := net.ParseDestination(network + ":" + addr)
|
dest, err := net.ParseDestination(network + ":" + addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var conn net.Conn
|
|
||||||
if dispatcher != nil {
|
|
||||||
dnsCtx := toDnsContext(ctx, s.dohURL)
|
dnsCtx := toDnsContext(ctx, s.dohURL)
|
||||||
if h2c {
|
if h2c {
|
||||||
dnsCtx = session.ContextWithMitmAlpn11(dnsCtx, false) // for insurance
|
dnsCtx = session.ContextWithMitmAlpn11(dnsCtx, false) // for insurance
|
||||||
dnsCtx = session.ContextWithMitmServerName(dnsCtx, url.Hostname())
|
dnsCtx = session.ContextWithMitmServerName(dnsCtx, url.Hostname())
|
||||||
}
|
}
|
||||||
link, err := dispatcher.Dispatch(dnsCtx, dest)
|
link, err := s.dispatcher.Dispatch(dnsCtx, dest)
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
default:
|
default:
|
||||||
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cc := common.ChainedClosable{}
|
cc := common.ChainedClosable{}
|
||||||
if cw, ok := link.Writer.(common.Closable); ok {
|
if cw, ok := link.Writer.(common.Closable); ok {
|
||||||
cc = append(cc, cw)
|
cc = append(cc, cw)
|
||||||
@ -82,57 +77,178 @@ func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, h2c bool, dis
|
|||||||
if cr, ok := link.Reader.(common.Closable); ok {
|
if cr, ok := link.Reader.(common.Closable); ok {
|
||||||
cc = append(cc, cr)
|
cc = append(cc, cr)
|
||||||
}
|
}
|
||||||
conn = cnc.NewConnection(
|
return cnc.NewConnection(
|
||||||
cnc.ConnectionInputMulti(link.Writer),
|
cnc.ConnectionInputMulti(link.Writer),
|
||||||
cnc.ConnectionOutputMulti(link.Reader),
|
cnc.ConnectionOutputMulti(link.Reader),
|
||||||
cnc.ConnectionOnClose(cc),
|
cnc.ConnectionOnClose(cc),
|
||||||
)
|
), nil
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
s.httpClient = &http.Client{
|
||||||
|
Timeout: time.Second * 180,
|
||||||
|
Transport: &http.Transport{
|
||||||
|
MaxIdleConns: 30,
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
TLSHandshakeTimeout: 30 * time.Second,
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
DialContext: dialContext,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if h2c {
|
||||||
|
s.httpClient.Transport = &http2.Transport{
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
|
||||||
|
return dialContext(ctx, network, addr)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDoHLocalNameServer creates DOH client object for local resolving
|
||||||
|
func NewDoHLocalNameServer(url *url.URL, queryStrategy QueryStrategy) *DoHNameServer {
|
||||||
|
url.Scheme = "https"
|
||||||
|
s := baseDOHNameServer(url, "DOHL", queryStrategy)
|
||||||
|
tr := &http.Transport{
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
dest, err := net.ParseDestination(network + ":" + addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn, err := internet.DialSystem(ctx, dest, nil)
|
||||||
log.Record(&log.AccessMessage{
|
log.Record(&log.AccessMessage{
|
||||||
From: "DNS",
|
From: "DNS",
|
||||||
To: s.dohURL,
|
To: s.dohURL,
|
||||||
Status: log.AccessAccepted,
|
Status: log.AccessAccepted,
|
||||||
Detour: "local",
|
Detour: "local",
|
||||||
})
|
})
|
||||||
conn, err = internet.DialSystem(ctx, dest, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if !h2c {
|
|
||||||
conn = utls.UClient(conn, &utls.Config{ServerName: url.Hostname()}, utls.HelloChrome_Auto)
|
|
||||||
if err := conn.(*utls.UConn).HandshakeContext(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return conn, nil
|
return conn, nil
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
|
s.httpClient = &http.Client{
|
||||||
|
Timeout: time.Second * 180,
|
||||||
|
Transport: tr,
|
||||||
|
}
|
||||||
|
errors.LogInfo(context.Background(), "DNS: created Local DNS-over-HTTPS client for ", url.String())
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func baseDOHNameServer(url *url.URL, prefix string, queryStrategy QueryStrategy) *DoHNameServer {
|
||||||
|
s := &DoHNameServer{
|
||||||
|
ips: make(map[string]*record),
|
||||||
|
pub: pubsub.NewService(),
|
||||||
|
name: prefix + "//" + url.Host,
|
||||||
|
dohURL: url.String(),
|
||||||
|
queryStrategy: queryStrategy,
|
||||||
|
}
|
||||||
|
s.cleanup = &task.Periodic{
|
||||||
|
Interval: time.Minute,
|
||||||
|
Execute: s.Cleanup,
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name implements Server.
|
// Name implements Server.
|
||||||
func (s *DoHNameServer) Name() string {
|
func (s *DoHNameServer) Name() string {
|
||||||
return s.cacheController.name
|
return s.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup clears expired items from cache
|
||||||
|
func (s *DoHNameServer) Cleanup() error {
|
||||||
|
now := time.Now()
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
|
||||||
|
if len(s.ips) == 0 {
|
||||||
|
return errors.New("nothing to do. stopping...")
|
||||||
|
}
|
||||||
|
|
||||||
|
for domain, record := range s.ips {
|
||||||
|
if record.A != nil && record.A.Expire.Before(now) {
|
||||||
|
record.A = nil
|
||||||
|
}
|
||||||
|
if record.AAAA != nil && record.AAAA.Expire.Before(now) {
|
||||||
|
record.AAAA = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if record.A == nil && record.AAAA == nil {
|
||||||
|
errors.LogDebug(context.Background(), s.name, " cleanup ", domain)
|
||||||
|
delete(s.ips, domain)
|
||||||
|
} else {
|
||||||
|
s.ips[domain] = record
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s.ips) == 0 {
|
||||||
|
s.ips = make(map[string]*record)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DoHNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) {
|
||||||
|
elapsed := time.Since(req.start)
|
||||||
|
|
||||||
|
s.Lock()
|
||||||
|
rec, found := s.ips[req.domain]
|
||||||
|
if !found {
|
||||||
|
rec = &record{}
|
||||||
|
}
|
||||||
|
updated := false
|
||||||
|
|
||||||
|
switch req.reqType {
|
||||||
|
case dnsmessage.TypeA:
|
||||||
|
if isNewer(rec.A, ipRec) {
|
||||||
|
rec.A = ipRec
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
case dnsmessage.TypeAAAA:
|
||||||
|
addr := make([]net.Address, 0, len(ipRec.IP))
|
||||||
|
for _, ip := range ipRec.IP {
|
||||||
|
if len(ip.IP()) == net.IPv6len {
|
||||||
|
addr = append(addr, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ipRec.IP = addr
|
||||||
|
if isNewer(rec.AAAA, ipRec) {
|
||||||
|
rec.AAAA = ipRec
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errors.LogInfo(context.Background(), s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed)
|
||||||
|
|
||||||
|
if updated {
|
||||||
|
s.ips[req.domain] = rec
|
||||||
|
}
|
||||||
|
switch req.reqType {
|
||||||
|
case dnsmessage.TypeA:
|
||||||
|
s.pub.Publish(req.domain+"4", nil)
|
||||||
|
case dnsmessage.TypeAAAA:
|
||||||
|
s.pub.Publish(req.domain+"6", nil)
|
||||||
|
}
|
||||||
|
s.Unlock()
|
||||||
|
common.Must(s.cleanup.Start())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DoHNameServer) newReqID() uint16 {
|
func (s *DoHNameServer) newReqID() uint16 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DoHNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, domain string, option dns_feature.IPOption) {
|
func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
|
||||||
errors.LogInfo(ctx, s.Name(), " querying: ", domain)
|
errors.LogInfo(ctx, s.name, " querying: ", domain)
|
||||||
|
|
||||||
if s.Name()+"." == "DOH//"+domain {
|
if s.name+"." == "DOH//"+domain {
|
||||||
errors.LogError(ctx, s.Name(), " tries to resolve itself! Use IP or set \"hosts\" instead.")
|
errors.LogError(ctx, s.name, " tries to resolve itself! Use IP or set \"hosts\" instead.")
|
||||||
noResponseErrCh <- errors.New("tries to resolve itself!", s.Name())
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// As we don't want our traffic pattern looks like DoH, we use Random-Length Padding instead of Block-Length Padding recommended in RFC 8467
|
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
|
||||||
// Although DoH server like 1.1.1.1 will pad the response to Block-Length 468, at least it is better than no padding for response at all
|
|
||||||
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP, int(crypto.RandBetween(100, 300))))
|
|
||||||
|
|
||||||
var deadline time.Time
|
var deadline time.Time
|
||||||
if d, ok := ctx.Deadline(); ok {
|
if d, ok := ctx.Deadline(); ok {
|
||||||
@ -167,22 +283,19 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- er
|
|||||||
b, err := dns.PackMessage(r.msg)
|
b, err := dns.PackMessage(r.msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(ctx, err, "failed to pack dns query for ", domain)
|
errors.LogErrorInner(ctx, err, "failed to pack dns query for ", domain)
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resp, err := s.dohHTTPSContext(dnsCtx, b.Bytes())
|
resp, err := s.dohHTTPSContext(dnsCtx, b.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(ctx, err, "failed to retrieve response for ", domain)
|
errors.LogErrorInner(ctx, err, "failed to retrieve response for ", domain)
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rec, err := parseResponse(resp)
|
rec, err := parseResponse(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(ctx, err, "failed to handle DOH response for ", domain)
|
errors.LogErrorInner(ctx, err, "failed to handle DOH response for ", domain)
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.cacheController.updateIP(r, rec)
|
s.updateIP(r, rec)
|
||||||
}(req)
|
}(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -197,8 +310,6 @@ func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte,
|
|||||||
req.Header.Add("Accept", "application/dns-message")
|
req.Header.Add("Accept", "application/dns-message")
|
||||||
req.Header.Add("Content-Type", "application/dns-message")
|
req.Header.Add("Content-Type", "application/dns-message")
|
||||||
|
|
||||||
req.Header.Set("X-Padding", strings.Repeat("X", int(crypto.RandBetween(100, 1000))))
|
|
||||||
|
|
||||||
hc := s.httpClient
|
hc := s.httpClient
|
||||||
|
|
||||||
resp, err := hc.Do(req.WithContext(ctx))
|
resp, err := hc.Do(req.WithContext(ctx))
|
||||||
@ -215,50 +326,107 @@ func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte,
|
|||||||
return io.ReadAll(resp.Body)
|
return io.ReadAll(resp.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DoHNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
|
||||||
|
s.RLock()
|
||||||
|
record, found := s.ips[domain]
|
||||||
|
s.RUnlock()
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil, errRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
var err4 error
|
||||||
|
var err6 error
|
||||||
|
var ips []net.Address
|
||||||
|
var ip6 []net.Address
|
||||||
|
|
||||||
|
if option.IPv4Enable {
|
||||||
|
ips, err4 = record.A.getIPs()
|
||||||
|
}
|
||||||
|
|
||||||
|
if option.IPv6Enable {
|
||||||
|
ip6, err6 = record.AAAA.getIPs()
|
||||||
|
ips = append(ips, ip6...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ips) > 0 {
|
||||||
|
return toNetIP(ips)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err4 != nil {
|
||||||
|
return nil, err4
|
||||||
|
}
|
||||||
|
|
||||||
|
if err6 != nil {
|
||||||
|
return nil, err6
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.IPv4Enable && record.A != nil) || (option.IPv6Enable && record.AAAA != nil) {
|
||||||
|
return nil, dns_feature.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
// QueryIP implements Server.
|
// QueryIP implements Server.
|
||||||
func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) { // nolint: dupl
|
func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) { // nolint: dupl
|
||||||
fqdn := Fqdn(domain)
|
fqdn := Fqdn(domain)
|
||||||
sub4, sub6 := s.cacheController.registerSubscribers(fqdn, option)
|
option = ResolveIpOptionOverride(s.queryStrategy, option)
|
||||||
defer closeSubscribers(sub4, sub6)
|
if !option.IPv4Enable && !option.IPv6Enable {
|
||||||
|
return nil, dns_feature.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
|
||||||
if s.cacheController.disableCache {
|
if disableCache {
|
||||||
errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.Name())
|
errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.name)
|
||||||
} else {
|
} else {
|
||||||
ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
|
ips, err := s.findIPsForDomain(fqdn, option)
|
||||||
if !go_errors.Is(err, errRecordNotFound) {
|
if err == nil || err == dns_feature.ErrEmptyResponse {
|
||||||
errors.LogDebugInner(ctx, err, s.Name(), " cache HIT ", domain, " -> ", ips)
|
errors.LogDebugInner(ctx, err, s.name, " cache HIT ", domain, " -> ", ips)
|
||||||
log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
|
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
|
||||||
return ips, ttl, err
|
return ips, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
noResponseErrCh := make(chan error, 2)
|
// ipv4 and ipv6 belong to different subscription groups
|
||||||
s.sendQuery(ctx, noResponseErrCh, fqdn, option)
|
var sub4, sub6 *pubsub.Subscriber
|
||||||
start := time.Now()
|
if option.IPv4Enable {
|
||||||
|
sub4 = s.pub.Subscribe(fqdn + "4")
|
||||||
|
defer sub4.Close()
|
||||||
|
}
|
||||||
|
if option.IPv6Enable {
|
||||||
|
sub6 = s.pub.Subscribe(fqdn + "6")
|
||||||
|
defer sub6.Close()
|
||||||
|
}
|
||||||
|
done := make(chan interface{})
|
||||||
|
go func() {
|
||||||
if sub4 != nil {
|
if sub4 != nil {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, 0, ctx.Err()
|
|
||||||
case err := <-noResponseErrCh:
|
|
||||||
return nil, 0, err
|
|
||||||
case <-sub4.Wait():
|
case <-sub4.Wait():
|
||||||
sub4.Close()
|
case <-ctx.Done():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if sub6 != nil {
|
if sub6 != nil {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, 0, ctx.Err()
|
|
||||||
case err := <-noResponseErrCh:
|
|
||||||
return nil, 0, err
|
|
||||||
case <-sub6.Wait():
|
case <-sub6.Wait():
|
||||||
sub6.Close()
|
case <-ctx.Done():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
s.sendQuery(ctx, fqdn, clientIP, option)
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
for {
|
||||||
|
ips, err := s.findIPsForDomain(fqdn, option)
|
||||||
|
if err != errRecordNotFound {
|
||||||
|
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
|
||||||
|
return ips, err
|
||||||
|
}
|
||||||
|
|
||||||
ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
|
select {
|
||||||
log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
|
case <-ctx.Done():
|
||||||
return ips, ttl, err
|
return nil, ctx.Err()
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,12 @@ func TestDOHNameServer(t *testing.T) {
|
|||||||
url, err := url.Parse("https+local://1.1.1.1/dns-query")
|
url, err := url.Parse("https+local://1.1.1.1/dns-query")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
s := NewDoHNameServer(url, nil, false, false, net.IP(nil))
|
s := NewDoHLocalNameServer(url, QueryStrategy_USE_IP)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
})
|
}, false)
|
||||||
cancel()
|
cancel()
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if len(ips) == 0 {
|
if len(ips) == 0 {
|
||||||
@ -34,12 +34,12 @@ func TestDOHNameServerWithCache(t *testing.T) {
|
|||||||
url, err := url.Parse("https+local://1.1.1.1/dns-query")
|
url, err := url.Parse("https+local://1.1.1.1/dns-query")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
s := NewDoHNameServer(url, nil, false, false, net.IP(nil))
|
s := NewDoHLocalNameServer(url, QueryStrategy_USE_IP)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
})
|
}, false)
|
||||||
cancel()
|
cancel()
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if len(ips) == 0 {
|
if len(ips) == 0 {
|
||||||
@ -47,10 +47,10 @@ func TestDOHNameServerWithCache(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
ips2, _, err := s.QueryIP(ctx2, "google.com", dns_feature.IPOption{
|
ips2, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
})
|
}, true)
|
||||||
cancel()
|
cancel()
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if r := cmp.Diff(ips2, ips); r != "" {
|
if r := cmp.Diff(ips2, ips); r != "" {
|
||||||
@ -62,12 +62,12 @@ func TestDOHNameServerWithIPv4Override(t *testing.T) {
|
|||||||
url, err := url.Parse("https+local://1.1.1.1/dns-query")
|
url, err := url.Parse("https+local://1.1.1.1/dns-query")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
s := NewDoHNameServer(url, nil, false, false, net.IP(nil))
|
s := NewDoHLocalNameServer(url, QueryStrategy_USE_IP4)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: false,
|
IPv6Enable: true,
|
||||||
})
|
}, false)
|
||||||
cancel()
|
cancel()
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if len(ips) == 0 {
|
if len(ips) == 0 {
|
||||||
@ -85,12 +85,12 @@ func TestDOHNameServerWithIPv6Override(t *testing.T) {
|
|||||||
url, err := url.Parse("https+local://1.1.1.1/dns-query")
|
url, err := url.Parse("https+local://1.1.1.1/dns-query")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
s := NewDoHNameServer(url, nil, false, false, net.IP(nil))
|
s := NewDoHLocalNameServer(url, QueryStrategy_USE_IP6)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
IPv4Enable: false,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
})
|
}, false)
|
||||||
cancel()
|
cancel()
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if len(ips) == 0 {
|
if len(ips) == 0 {
|
||||||
|
@ -20,9 +20,9 @@ func (FakeDNSServer) Name() string {
|
|||||||
return "FakeDNS"
|
return "FakeDNS"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, opt dns.IPOption) ([]net.IP, uint32, error) {
|
func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ net.IP, opt dns.IPOption, _ bool) ([]net.IP, error) {
|
||||||
if f.fakeDNSEngine == nil {
|
if f.fakeDNSEngine == nil {
|
||||||
return nil, 0, errors.New("Unable to locate a fake DNS Engine").AtError()
|
return nil, errors.New("Unable to locate a fake DNS Engine").AtError()
|
||||||
}
|
}
|
||||||
|
|
||||||
var ips []net.Address
|
var ips []net.Address
|
||||||
@ -34,13 +34,13 @@ func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, opt dns.IPOp
|
|||||||
|
|
||||||
netIP, err := toNetIP(ips)
|
netIP, err := toNetIP(ips)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, errors.New("Unable to convert IP to net ip").Base(err).AtError()
|
return nil, errors.New("Unable to convert IP to net ip").Base(err).AtError()
|
||||||
}
|
}
|
||||||
|
|
||||||
errors.LogInfo(ctx, f.Name(), " got answer: ", domain, " -> ", ips)
|
errors.LogInfo(ctx, f.Name(), " got answer: ", domain, " -> ", ips)
|
||||||
|
|
||||||
if len(netIP) > 0 {
|
if len(netIP) > 0 {
|
||||||
return netIP, 1, nil // fakeIP ttl is 1
|
return netIP, nil
|
||||||
}
|
}
|
||||||
return nil, 0, dns.ErrEmptyResponse
|
return nil, dns.ErrEmptyResponse
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
@ -14,13 +15,24 @@ import (
|
|||||||
// LocalNameServer is an wrapper over local DNS feature.
|
// LocalNameServer is an wrapper over local DNS feature.
|
||||||
type LocalNameServer struct {
|
type LocalNameServer struct {
|
||||||
client *localdns.Client
|
client *localdns.Client
|
||||||
|
queryStrategy QueryStrategy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const errEmptyResponse = "No address associated with hostname"
|
||||||
|
|
||||||
// QueryIP implements Server.
|
// QueryIP implements Server.
|
||||||
func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, option dns.IPOption) (ips []net.IP, ttl uint32, err error) {
|
func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, _ net.IP, option dns.IPOption, _ bool) (ips []net.IP, err error) {
|
||||||
|
option = ResolveIpOptionOverride(s.queryStrategy, option)
|
||||||
|
if !option.IPv4Enable && !option.IPv6Enable {
|
||||||
|
return nil, dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
ips, ttl, err = s.client.LookupIP(domain, option)
|
ips, err = s.client.LookupIP(domain, option)
|
||||||
|
|
||||||
|
if err != nil && strings.HasSuffix(err.Error(), errEmptyResponse) {
|
||||||
|
err = dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
|
||||||
if len(ips) > 0 {
|
if len(ips) > 0 {
|
||||||
errors.LogInfo(ctx, "Localhost got answer: ", domain, " -> ", ips)
|
errors.LogInfo(ctx, "Localhost got answer: ", domain, " -> ", ips)
|
||||||
@ -36,14 +48,15 @@ func (s *LocalNameServer) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewLocalNameServer creates localdns server object for directly lookup in system DNS.
|
// NewLocalNameServer creates localdns server object for directly lookup in system DNS.
|
||||||
func NewLocalNameServer() *LocalNameServer {
|
func NewLocalNameServer(queryStrategy QueryStrategy) *LocalNameServer {
|
||||||
errors.LogInfo(context.Background(), "DNS: created localhost client")
|
errors.LogInfo(context.Background(), "DNS: created localhost client")
|
||||||
return &LocalNameServer{
|
return &LocalNameServer{
|
||||||
|
queryStrategy: queryStrategy,
|
||||||
client: localdns.New(),
|
client: localdns.New(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLocalDNSClient creates localdns client object for directly lookup in system DNS.
|
// NewLocalDNSClient creates localdns client object for directly lookup in system DNS.
|
||||||
func NewLocalDNSClient(ipOption dns.IPOption) *Client {
|
func NewLocalDNSClient() *Client {
|
||||||
return &Client{server: NewLocalNameServer(), ipOption: &ipOption}
|
return &Client{server: NewLocalNameServer(QueryStrategy_USE_IP)}
|
||||||
}
|
}
|
||||||
|
@ -7,17 +7,18 @@ import (
|
|||||||
|
|
||||||
. "github.com/xtls/xray-core/app/dns"
|
. "github.com/xtls/xray-core/app/dns"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/features/dns"
|
"github.com/xtls/xray-core/features/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLocalNameServer(t *testing.T) {
|
func TestLocalNameServer(t *testing.T) {
|
||||||
s := NewLocalNameServer()
|
s := NewLocalNameServer(QueryStrategy_USE_IP)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
||||||
ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{
|
ips, err := s.QueryIP(ctx, "google.com", net.IP{}, dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
})
|
}, false)
|
||||||
cancel()
|
cancel()
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if len(ips) == 0 {
|
if len(ips) == 0 {
|
||||||
|
@ -4,20 +4,23 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
go_errors "errors"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/quic-go/quic-go"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/log"
|
"github.com/xtls/xray-core/common/log"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/protocol/dns"
|
"github.com/xtls/xray-core/common/protocol/dns"
|
||||||
"github.com/xtls/xray-core/common/session"
|
"github.com/xtls/xray-core/common/session"
|
||||||
|
"github.com/xtls/xray-core/common/signal/pubsub"
|
||||||
|
"github.com/xtls/xray-core/common/task"
|
||||||
dns_feature "github.com/xtls/xray-core/features/dns"
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
"github.com/xtls/xray-core/transport/internet/tls"
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -30,14 +33,17 @@ const handshakeTimeout = time.Second * 8
|
|||||||
// QUICNameServer implemented DNS over QUIC
|
// QUICNameServer implemented DNS over QUIC
|
||||||
type QUICNameServer struct {
|
type QUICNameServer struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
cacheController *CacheController
|
ips map[string]*record
|
||||||
|
pub *pubsub.Service
|
||||||
|
cleanup *task.Periodic
|
||||||
|
name string
|
||||||
destination *net.Destination
|
destination *net.Destination
|
||||||
connection quic.Connection
|
connection quic.Connection
|
||||||
clientIP net.IP
|
queryStrategy QueryStrategy
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewQUICNameServer creates DNS-over-QUIC client object for local resolving
|
// NewQUICNameServer creates DNS-over-QUIC client object for local resolving
|
||||||
func NewQUICNameServer(url *url.URL, disableCache bool, clientIP net.IP) (*QUICNameServer, error) {
|
func NewQUICNameServer(url *url.URL, queryStrategy QueryStrategy) (*QUICNameServer, error) {
|
||||||
errors.LogInfo(context.Background(), "DNS: created Local DNS-over-QUIC client for ", url.String())
|
errors.LogInfo(context.Background(), "DNS: created Local DNS-over-QUIC client for ", url.String())
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@ -51,9 +57,15 @@ func NewQUICNameServer(url *url.URL, disableCache bool, clientIP net.IP) (*QUICN
|
|||||||
dest := net.UDPDestination(net.ParseAddress(url.Hostname()), port)
|
dest := net.UDPDestination(net.ParseAddress(url.Hostname()), port)
|
||||||
|
|
||||||
s := &QUICNameServer{
|
s := &QUICNameServer{
|
||||||
cacheController: NewCacheController(url.String(), disableCache),
|
ips: make(map[string]*record),
|
||||||
|
pub: pubsub.NewService(),
|
||||||
|
name: url.String(),
|
||||||
destination: &dest,
|
destination: &dest,
|
||||||
clientIP: clientIP,
|
queryStrategy: queryStrategy,
|
||||||
|
}
|
||||||
|
s.cleanup = &task.Periodic{
|
||||||
|
Interval: time.Minute,
|
||||||
|
Execute: s.Cleanup,
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
@ -61,17 +73,94 @@ func NewQUICNameServer(url *url.URL, disableCache bool, clientIP net.IP) (*QUICN
|
|||||||
|
|
||||||
// Name returns client name
|
// Name returns client name
|
||||||
func (s *QUICNameServer) Name() string {
|
func (s *QUICNameServer) Name() string {
|
||||||
return s.cacheController.name
|
return s.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup clears expired items from cache
|
||||||
|
func (s *QUICNameServer) Cleanup() error {
|
||||||
|
now := time.Now()
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
|
||||||
|
if len(s.ips) == 0 {
|
||||||
|
return errors.New("nothing to do. stopping...")
|
||||||
|
}
|
||||||
|
|
||||||
|
for domain, record := range s.ips {
|
||||||
|
if record.A != nil && record.A.Expire.Before(now) {
|
||||||
|
record.A = nil
|
||||||
|
}
|
||||||
|
if record.AAAA != nil && record.AAAA.Expire.Before(now) {
|
||||||
|
record.AAAA = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if record.A == nil && record.AAAA == nil {
|
||||||
|
errors.LogDebug(context.Background(), s.name, " cleanup ", domain)
|
||||||
|
delete(s.ips, domain)
|
||||||
|
} else {
|
||||||
|
s.ips[domain] = record
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s.ips) == 0 {
|
||||||
|
s.ips = make(map[string]*record)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QUICNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) {
|
||||||
|
elapsed := time.Since(req.start)
|
||||||
|
|
||||||
|
s.Lock()
|
||||||
|
rec, found := s.ips[req.domain]
|
||||||
|
if !found {
|
||||||
|
rec = &record{}
|
||||||
|
}
|
||||||
|
updated := false
|
||||||
|
|
||||||
|
switch req.reqType {
|
||||||
|
case dnsmessage.TypeA:
|
||||||
|
if isNewer(rec.A, ipRec) {
|
||||||
|
rec.A = ipRec
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
case dnsmessage.TypeAAAA:
|
||||||
|
addr := make([]net.Address, 0)
|
||||||
|
for _, ip := range ipRec.IP {
|
||||||
|
if len(ip.IP()) == net.IPv6len {
|
||||||
|
addr = append(addr, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ipRec.IP = addr
|
||||||
|
if isNewer(rec.AAAA, ipRec) {
|
||||||
|
rec.AAAA = ipRec
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errors.LogInfo(context.Background(), s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed)
|
||||||
|
|
||||||
|
if updated {
|
||||||
|
s.ips[req.domain] = rec
|
||||||
|
}
|
||||||
|
switch req.reqType {
|
||||||
|
case dnsmessage.TypeA:
|
||||||
|
s.pub.Publish(req.domain+"4", nil)
|
||||||
|
case dnsmessage.TypeAAAA:
|
||||||
|
s.pub.Publish(req.domain+"6", nil)
|
||||||
|
}
|
||||||
|
s.Unlock()
|
||||||
|
common.Must(s.cleanup.Start())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *QUICNameServer) newReqID() uint16 {
|
func (s *QUICNameServer) newReqID() uint16 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *QUICNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, domain string, option dns_feature.IPOption) {
|
func (s *QUICNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
|
||||||
errors.LogInfo(ctx, s.Name(), " querying: ", domain)
|
errors.LogInfo(ctx, s.name, " querying: ", domain)
|
||||||
|
|
||||||
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
|
||||||
|
|
||||||
var deadline time.Time
|
var deadline time.Time
|
||||||
if d, ok := ctx.Deadline(); ok {
|
if d, ok := ctx.Deadline(); ok {
|
||||||
@ -103,36 +192,23 @@ func (s *QUICNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- e
|
|||||||
b, err := dns.PackMessage(r.msg)
|
b, err := dns.PackMessage(r.msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(ctx, err, "failed to pack dns query")
|
errors.LogErrorInner(ctx, err, "failed to pack dns query")
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dnsReqBuf := buf.New()
|
dnsReqBuf := buf.New()
|
||||||
err = binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len()))
|
binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len()))
|
||||||
if err != nil {
|
dnsReqBuf.Write(b.Bytes())
|
||||||
errors.LogErrorInner(ctx, err, "binary write failed")
|
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = dnsReqBuf.Write(b.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
errors.LogErrorInner(ctx, err, "buffer write failed")
|
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b.Release()
|
b.Release()
|
||||||
|
|
||||||
conn, err := s.openStream(dnsCtx)
|
conn, err := s.openStream(dnsCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(ctx, err, "failed to open quic connection")
|
errors.LogErrorInner(ctx, err, "failed to open quic connection")
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = conn.Write(dnsReqBuf.Bytes())
|
_, err = conn.Write(dnsReqBuf.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(ctx, err, "failed to send query")
|
errors.LogErrorInner(ctx, err, "failed to send query")
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,81 +219,134 @@ func (s *QUICNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- e
|
|||||||
n, err := respBuf.ReadFullFrom(conn, 2)
|
n, err := respBuf.ReadFullFrom(conn, 2)
|
||||||
if err != nil && n == 0 {
|
if err != nil && n == 0 {
|
||||||
errors.LogErrorInner(ctx, err, "failed to read response length")
|
errors.LogErrorInner(ctx, err, "failed to read response length")
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var length int16
|
var length int16
|
||||||
err = binary.Read(bytes.NewReader(respBuf.Bytes()), binary.BigEndian, &length)
|
err = binary.Read(bytes.NewReader(respBuf.Bytes()), binary.BigEndian, &length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(ctx, err, "failed to parse response length")
|
errors.LogErrorInner(ctx, err, "failed to parse response length")
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
respBuf.Clear()
|
respBuf.Clear()
|
||||||
n, err = respBuf.ReadFullFrom(conn, int32(length))
|
n, err = respBuf.ReadFullFrom(conn, int32(length))
|
||||||
if err != nil && n == 0 {
|
if err != nil && n == 0 {
|
||||||
errors.LogErrorInner(ctx, err, "failed to read response length")
|
errors.LogErrorInner(ctx, err, "failed to read response length")
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rec, err := parseResponse(respBuf.Bytes())
|
rec, err := parseResponse(respBuf.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(ctx, err, "failed to handle response")
|
errors.LogErrorInner(ctx, err, "failed to handle response")
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.cacheController.updateIP(r, rec)
|
s.updateIP(r, rec)
|
||||||
}(req)
|
}(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *QUICNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
|
||||||
|
s.RLock()
|
||||||
|
record, found := s.ips[domain]
|
||||||
|
s.RUnlock()
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil, errRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
var err4 error
|
||||||
|
var err6 error
|
||||||
|
var ips []net.Address
|
||||||
|
var ip6 []net.Address
|
||||||
|
|
||||||
|
if option.IPv4Enable {
|
||||||
|
ips, err4 = record.A.getIPs()
|
||||||
|
}
|
||||||
|
|
||||||
|
if option.IPv6Enable {
|
||||||
|
ip6, err6 = record.AAAA.getIPs()
|
||||||
|
ips = append(ips, ip6...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ips) > 0 {
|
||||||
|
return toNetIP(ips)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err4 != nil {
|
||||||
|
return nil, err4
|
||||||
|
}
|
||||||
|
|
||||||
|
if err6 != nil {
|
||||||
|
return nil, err6
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.IPv4Enable && record.A != nil) || (option.IPv6Enable && record.AAAA != nil) {
|
||||||
|
return nil, dns_feature.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
// QueryIP is called from dns.Server->queryIPTimeout
|
// QueryIP is called from dns.Server->queryIPTimeout
|
||||||
func (s *QUICNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
|
func (s *QUICNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) {
|
||||||
fqdn := Fqdn(domain)
|
fqdn := Fqdn(domain)
|
||||||
sub4, sub6 := s.cacheController.registerSubscribers(fqdn, option)
|
option = ResolveIpOptionOverride(s.queryStrategy, option)
|
||||||
defer closeSubscribers(sub4, sub6)
|
if !option.IPv4Enable && !option.IPv6Enable {
|
||||||
|
return nil, dns_feature.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
|
||||||
if s.cacheController.disableCache {
|
if disableCache {
|
||||||
errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.Name())
|
errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.name)
|
||||||
} else {
|
} else {
|
||||||
ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
|
ips, err := s.findIPsForDomain(fqdn, option)
|
||||||
if !go_errors.Is(err, errRecordNotFound) {
|
if err == nil || err == dns_feature.ErrEmptyResponse {
|
||||||
errors.LogDebugInner(ctx, err, s.Name(), " cache HIT ", domain, " -> ", ips)
|
errors.LogDebugInner(ctx, err, s.name, " cache HIT ", domain, " -> ", ips)
|
||||||
log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
|
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
|
||||||
return ips, ttl, err
|
return ips, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
noResponseErrCh := make(chan error, 2)
|
// ipv4 and ipv6 belong to different subscription groups
|
||||||
s.sendQuery(ctx, noResponseErrCh, fqdn, option)
|
var sub4, sub6 *pubsub.Subscriber
|
||||||
start := time.Now()
|
if option.IPv4Enable {
|
||||||
|
sub4 = s.pub.Subscribe(fqdn + "4")
|
||||||
|
defer sub4.Close()
|
||||||
|
}
|
||||||
|
if option.IPv6Enable {
|
||||||
|
sub6 = s.pub.Subscribe(fqdn + "6")
|
||||||
|
defer sub6.Close()
|
||||||
|
}
|
||||||
|
done := make(chan interface{})
|
||||||
|
go func() {
|
||||||
if sub4 != nil {
|
if sub4 != nil {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, 0, ctx.Err()
|
|
||||||
case err := <-noResponseErrCh:
|
|
||||||
return nil, 0, err
|
|
||||||
case <-sub4.Wait():
|
case <-sub4.Wait():
|
||||||
sub4.Close()
|
case <-ctx.Done():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if sub6 != nil {
|
if sub6 != nil {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, 0, ctx.Err()
|
|
||||||
case err := <-noResponseErrCh:
|
|
||||||
return nil, 0, err
|
|
||||||
case <-sub6.Wait():
|
case <-sub6.Wait():
|
||||||
sub6.Close()
|
case <-ctx.Done():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
s.sendQuery(ctx, fqdn, clientIP, option)
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
|
for {
|
||||||
log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
|
ips, err := s.findIPsForDomain(fqdn, option)
|
||||||
return ips, ttl, err
|
if err != errRecordNotFound {
|
||||||
|
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
|
||||||
|
return ips, err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isActive(s quic.Connection) bool {
|
func isActive(s quic.Connection) bool {
|
||||||
|
@ -16,23 +16,24 @@ import (
|
|||||||
func TestQUICNameServer(t *testing.T) {
|
func TestQUICNameServer(t *testing.T) {
|
||||||
url, err := url.Parse("quic://dns.adguard-dns.com")
|
url, err := url.Parse("quic://dns.adguard-dns.com")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
s, err := NewQUICNameServer(url, false, net.IP(nil))
|
s, err := NewQUICNameServer(url, QueryStrategy_USE_IP)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
||||||
ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{
|
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
})
|
}, false)
|
||||||
cancel()
|
cancel()
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if len(ips) == 0 {
|
if len(ips) == 0 {
|
||||||
t.Error("expect some ips, but got 0")
|
t.Error("expect some ips, but got 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
ips2, _, err := s.QueryIP(ctx2, "google.com", dns.IPOption{
|
ips2, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
})
|
}, true)
|
||||||
cancel()
|
cancel()
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if r := cmp.Diff(ips2, ips); r != "" {
|
if r := cmp.Diff(ips2, ips); r != "" {
|
||||||
@ -43,13 +44,13 @@ func TestQUICNameServer(t *testing.T) {
|
|||||||
func TestQUICNameServerWithIPv4Override(t *testing.T) {
|
func TestQUICNameServerWithIPv4Override(t *testing.T) {
|
||||||
url, err := url.Parse("quic://dns.adguard-dns.com")
|
url, err := url.Parse("quic://dns.adguard-dns.com")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
s, err := NewQUICNameServer(url, false, net.IP(nil))
|
s, err := NewQUICNameServer(url, QueryStrategy_USE_IP4)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
||||||
ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{
|
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: false,
|
IPv6Enable: true,
|
||||||
})
|
}, false)
|
||||||
cancel()
|
cancel()
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if len(ips) == 0 {
|
if len(ips) == 0 {
|
||||||
@ -66,13 +67,13 @@ func TestQUICNameServerWithIPv4Override(t *testing.T) {
|
|||||||
func TestQUICNameServerWithIPv6Override(t *testing.T) {
|
func TestQUICNameServerWithIPv6Override(t *testing.T) {
|
||||||
url, err := url.Parse("quic://dns.adguard-dns.com")
|
url, err := url.Parse("quic://dns.adguard-dns.com")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
s, err := NewQUICNameServer(url, false, net.IP(nil))
|
s, err := NewQUICNameServer(url, QueryStrategy_USE_IP6)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
||||||
ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{
|
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns.IPOption{
|
||||||
IPv4Enable: false,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
})
|
}, false)
|
||||||
cancel()
|
cancel()
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if len(ips) == 0 {
|
if len(ips) == 0 {
|
||||||
|
@ -4,11 +4,12 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
go_errors "errors"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/log"
|
"github.com/xtls/xray-core/common/log"
|
||||||
@ -16,28 +17,34 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/net/cnc"
|
"github.com/xtls/xray-core/common/net/cnc"
|
||||||
"github.com/xtls/xray-core/common/protocol/dns"
|
"github.com/xtls/xray-core/common/protocol/dns"
|
||||||
"github.com/xtls/xray-core/common/session"
|
"github.com/xtls/xray-core/common/session"
|
||||||
|
"github.com/xtls/xray-core/common/signal/pubsub"
|
||||||
|
"github.com/xtls/xray-core/common/task"
|
||||||
dns_feature "github.com/xtls/xray-core/features/dns"
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
"github.com/xtls/xray-core/features/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TCPNameServer implemented DNS over TCP (RFC7766).
|
// TCPNameServer implemented DNS over TCP (RFC7766).
|
||||||
type TCPNameServer struct {
|
type TCPNameServer struct {
|
||||||
cacheController *CacheController
|
sync.RWMutex
|
||||||
|
name string
|
||||||
destination *net.Destination
|
destination *net.Destination
|
||||||
|
ips map[string]*record
|
||||||
|
pub *pubsub.Service
|
||||||
|
cleanup *task.Periodic
|
||||||
reqID uint32
|
reqID uint32
|
||||||
dial func(context.Context) (net.Conn, error)
|
dial func(context.Context) (net.Conn, error)
|
||||||
clientIP net.IP
|
queryStrategy QueryStrategy
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTCPNameServer creates DNS over TCP server object for remote resolving.
|
// NewTCPNameServer creates DNS over TCP server object for remote resolving.
|
||||||
func NewTCPNameServer(
|
func NewTCPNameServer(
|
||||||
url *url.URL,
|
url *url.URL,
|
||||||
dispatcher routing.Dispatcher,
|
dispatcher routing.Dispatcher,
|
||||||
disableCache bool,
|
queryStrategy QueryStrategy,
|
||||||
clientIP net.IP,
|
|
||||||
) (*TCPNameServer, error) {
|
) (*TCPNameServer, error) {
|
||||||
s, err := baseTCPNameServer(url, "TCP", disableCache, clientIP)
|
s, err := baseTCPNameServer(url, "TCP", queryStrategy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -58,8 +65,8 @@ func NewTCPNameServer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewTCPLocalNameServer creates DNS over TCP client object for local resolving
|
// NewTCPLocalNameServer creates DNS over TCP client object for local resolving
|
||||||
func NewTCPLocalNameServer(url *url.URL, disableCache bool, clientIP net.IP) (*TCPNameServer, error) {
|
func NewTCPLocalNameServer(url *url.URL, queryStrategy QueryStrategy) (*TCPNameServer, error) {
|
||||||
s, err := baseTCPNameServer(url, "TCPL", disableCache, clientIP)
|
s, err := baseTCPNameServer(url, "TCPL", queryStrategy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -71,7 +78,7 @@ func NewTCPLocalNameServer(url *url.URL, disableCache bool, clientIP net.IP) (*T
|
|||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func baseTCPNameServer(url *url.URL, prefix string, disableCache bool, clientIP net.IP) (*TCPNameServer, error) {
|
func baseTCPNameServer(url *url.URL, prefix string, queryStrategy QueryStrategy) (*TCPNameServer, error) {
|
||||||
port := net.Port(53)
|
port := net.Port(53)
|
||||||
if url.Port() != "" {
|
if url.Port() != "" {
|
||||||
var err error
|
var err error
|
||||||
@ -82,9 +89,15 @@ func baseTCPNameServer(url *url.URL, prefix string, disableCache bool, clientIP
|
|||||||
dest := net.TCPDestination(net.ParseAddress(url.Hostname()), port)
|
dest := net.TCPDestination(net.ParseAddress(url.Hostname()), port)
|
||||||
|
|
||||||
s := &TCPNameServer{
|
s := &TCPNameServer{
|
||||||
cacheController: NewCacheController(prefix+"//"+dest.NetAddr(), disableCache),
|
|
||||||
destination: &dest,
|
destination: &dest,
|
||||||
clientIP: clientIP,
|
ips: make(map[string]*record),
|
||||||
|
pub: pubsub.NewService(),
|
||||||
|
name: prefix + "//" + dest.NetAddr(),
|
||||||
|
queryStrategy: queryStrategy,
|
||||||
|
}
|
||||||
|
s.cleanup = &task.Periodic{
|
||||||
|
Interval: time.Minute,
|
||||||
|
Execute: s.Cleanup,
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
@ -92,17 +105,94 @@ func baseTCPNameServer(url *url.URL, prefix string, disableCache bool, clientIP
|
|||||||
|
|
||||||
// Name implements Server.
|
// Name implements Server.
|
||||||
func (s *TCPNameServer) Name() string {
|
func (s *TCPNameServer) Name() string {
|
||||||
return s.cacheController.name
|
return s.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup clears expired items from cache
|
||||||
|
func (s *TCPNameServer) Cleanup() error {
|
||||||
|
now := time.Now()
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
|
||||||
|
if len(s.ips) == 0 {
|
||||||
|
return errors.New("nothing to do. stopping...")
|
||||||
|
}
|
||||||
|
|
||||||
|
for domain, record := range s.ips {
|
||||||
|
if record.A != nil && record.A.Expire.Before(now) {
|
||||||
|
record.A = nil
|
||||||
|
}
|
||||||
|
if record.AAAA != nil && record.AAAA.Expire.Before(now) {
|
||||||
|
record.AAAA = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if record.A == nil && record.AAAA == nil {
|
||||||
|
errors.LogDebug(context.Background(), s.name, " cleanup ", domain)
|
||||||
|
delete(s.ips, domain)
|
||||||
|
} else {
|
||||||
|
s.ips[domain] = record
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s.ips) == 0 {
|
||||||
|
s.ips = make(map[string]*record)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TCPNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) {
|
||||||
|
elapsed := time.Since(req.start)
|
||||||
|
|
||||||
|
s.Lock()
|
||||||
|
rec, found := s.ips[req.domain]
|
||||||
|
if !found {
|
||||||
|
rec = &record{}
|
||||||
|
}
|
||||||
|
updated := false
|
||||||
|
|
||||||
|
switch req.reqType {
|
||||||
|
case dnsmessage.TypeA:
|
||||||
|
if isNewer(rec.A, ipRec) {
|
||||||
|
rec.A = ipRec
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
case dnsmessage.TypeAAAA:
|
||||||
|
addr := make([]net.Address, 0)
|
||||||
|
for _, ip := range ipRec.IP {
|
||||||
|
if len(ip.IP()) == net.IPv6len {
|
||||||
|
addr = append(addr, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ipRec.IP = addr
|
||||||
|
if isNewer(rec.AAAA, ipRec) {
|
||||||
|
rec.AAAA = ipRec
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errors.LogInfo(context.Background(), s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed)
|
||||||
|
|
||||||
|
if updated {
|
||||||
|
s.ips[req.domain] = rec
|
||||||
|
}
|
||||||
|
switch req.reqType {
|
||||||
|
case dnsmessage.TypeA:
|
||||||
|
s.pub.Publish(req.domain+"4", nil)
|
||||||
|
case dnsmessage.TypeAAAA:
|
||||||
|
s.pub.Publish(req.domain+"6", nil)
|
||||||
|
}
|
||||||
|
s.Unlock()
|
||||||
|
common.Must(s.cleanup.Start())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TCPNameServer) newReqID() uint16 {
|
func (s *TCPNameServer) newReqID() uint16 {
|
||||||
return uint16(atomic.AddUint32(&s.reqID, 1))
|
return uint16(atomic.AddUint32(&s.reqID, 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TCPNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, domain string, option dns_feature.IPOption) {
|
func (s *TCPNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
|
||||||
errors.LogDebug(ctx, s.Name(), " querying DNS for: ", domain)
|
errors.LogDebug(ctx, s.name, " querying DNS for: ", domain)
|
||||||
|
|
||||||
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
|
||||||
|
|
||||||
var deadline time.Time
|
var deadline time.Time
|
||||||
if d, ok := ctx.Deadline(); ok {
|
if d, ok := ctx.Deadline(); ok {
|
||||||
@ -131,36 +221,23 @@ func (s *TCPNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- er
|
|||||||
b, err := dns.PackMessage(r.msg)
|
b, err := dns.PackMessage(r.msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(ctx, err, "failed to pack dns query")
|
errors.LogErrorInner(ctx, err, "failed to pack dns query")
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := s.dial(dnsCtx)
|
conn, err := s.dial(dnsCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(ctx, err, "failed to dial namesever")
|
errors.LogErrorInner(ctx, err, "failed to dial namesever")
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
dnsReqBuf := buf.New()
|
dnsReqBuf := buf.New()
|
||||||
err = binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len()))
|
binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len()))
|
||||||
if err != nil {
|
dnsReqBuf.Write(b.Bytes())
|
||||||
errors.LogErrorInner(ctx, err, "binary write failed")
|
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = dnsReqBuf.Write(b.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
errors.LogErrorInner(ctx, err, "buffer write failed")
|
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b.Release()
|
b.Release()
|
||||||
|
|
||||||
_, err = conn.Write(dnsReqBuf.Bytes())
|
_, err = conn.Write(dnsReqBuf.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(ctx, err, "failed to send query")
|
errors.LogErrorInner(ctx, err, "failed to send query")
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dnsReqBuf.Release()
|
dnsReqBuf.Release()
|
||||||
@ -170,80 +247,129 @@ func (s *TCPNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- er
|
|||||||
n, err := respBuf.ReadFullFrom(conn, 2)
|
n, err := respBuf.ReadFullFrom(conn, 2)
|
||||||
if err != nil && n == 0 {
|
if err != nil && n == 0 {
|
||||||
errors.LogErrorInner(ctx, err, "failed to read response length")
|
errors.LogErrorInner(ctx, err, "failed to read response length")
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var length int16
|
var length int16
|
||||||
err = binary.Read(bytes.NewReader(respBuf.Bytes()), binary.BigEndian, &length)
|
err = binary.Read(bytes.NewReader(respBuf.Bytes()), binary.BigEndian, &length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(ctx, err, "failed to parse response length")
|
errors.LogErrorInner(ctx, err, "failed to parse response length")
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
respBuf.Clear()
|
respBuf.Clear()
|
||||||
n, err = respBuf.ReadFullFrom(conn, int32(length))
|
n, err = respBuf.ReadFullFrom(conn, int32(length))
|
||||||
if err != nil && n == 0 {
|
if err != nil && n == 0 {
|
||||||
errors.LogErrorInner(ctx, err, "failed to read response length")
|
errors.LogErrorInner(ctx, err, "failed to read response length")
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rec, err := parseResponse(respBuf.Bytes())
|
rec, err := parseResponse(respBuf.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(ctx, err, "failed to parse DNS over TCP response")
|
errors.LogErrorInner(ctx, err, "failed to parse DNS over TCP response")
|
||||||
noResponseErrCh <- err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.cacheController.updateIP(r, rec)
|
s.updateIP(r, rec)
|
||||||
}(req)
|
}(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *TCPNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
|
||||||
|
s.RLock()
|
||||||
|
record, found := s.ips[domain]
|
||||||
|
s.RUnlock()
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil, errRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
var err4 error
|
||||||
|
var err6 error
|
||||||
|
var ips []net.Address
|
||||||
|
var ip6 []net.Address
|
||||||
|
|
||||||
|
if option.IPv4Enable {
|
||||||
|
ips, err4 = record.A.getIPs()
|
||||||
|
}
|
||||||
|
|
||||||
|
if option.IPv6Enable {
|
||||||
|
ip6, err6 = record.AAAA.getIPs()
|
||||||
|
ips = append(ips, ip6...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ips) > 0 {
|
||||||
|
return toNetIP(ips)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err4 != nil {
|
||||||
|
return nil, err4
|
||||||
|
}
|
||||||
|
|
||||||
|
if err6 != nil {
|
||||||
|
return nil, err6
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, dns_feature.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
|
||||||
// QueryIP implements Server.
|
// QueryIP implements Server.
|
||||||
func (s *TCPNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
|
func (s *TCPNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) {
|
||||||
fqdn := Fqdn(domain)
|
fqdn := Fqdn(domain)
|
||||||
sub4, sub6 := s.cacheController.registerSubscribers(fqdn, option)
|
option = ResolveIpOptionOverride(s.queryStrategy, option)
|
||||||
defer closeSubscribers(sub4, sub6)
|
if !option.IPv4Enable && !option.IPv6Enable {
|
||||||
|
return nil, dns_feature.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
|
||||||
if s.cacheController.disableCache {
|
if disableCache {
|
||||||
errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.Name())
|
errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.name)
|
||||||
} else {
|
} else {
|
||||||
ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
|
ips, err := s.findIPsForDomain(fqdn, option)
|
||||||
if !go_errors.Is(err, errRecordNotFound) {
|
if err == nil || err == dns_feature.ErrEmptyResponse {
|
||||||
errors.LogDebugInner(ctx, err, s.Name(), " cache HIT ", domain, " -> ", ips)
|
errors.LogDebugInner(ctx, err, s.name, " cache HIT ", domain, " -> ", ips)
|
||||||
log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
|
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
|
||||||
return ips, ttl, err
|
return ips, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
noResponseErrCh := make(chan error, 2)
|
// ipv4 and ipv6 belong to different subscription groups
|
||||||
s.sendQuery(ctx, noResponseErrCh, fqdn, option)
|
var sub4, sub6 *pubsub.Subscriber
|
||||||
start := time.Now()
|
if option.IPv4Enable {
|
||||||
|
sub4 = s.pub.Subscribe(fqdn + "4")
|
||||||
|
defer sub4.Close()
|
||||||
|
}
|
||||||
|
if option.IPv6Enable {
|
||||||
|
sub6 = s.pub.Subscribe(fqdn + "6")
|
||||||
|
defer sub6.Close()
|
||||||
|
}
|
||||||
|
done := make(chan interface{})
|
||||||
|
go func() {
|
||||||
if sub4 != nil {
|
if sub4 != nil {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, 0, ctx.Err()
|
|
||||||
case err := <-noResponseErrCh:
|
|
||||||
return nil, 0, err
|
|
||||||
case <-sub4.Wait():
|
case <-sub4.Wait():
|
||||||
sub4.Close()
|
case <-ctx.Done():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if sub6 != nil {
|
if sub6 != nil {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, 0, ctx.Err()
|
|
||||||
case err := <-noResponseErrCh:
|
|
||||||
return nil, 0, err
|
|
||||||
case <-sub6.Wait():
|
case <-sub6.Wait():
|
||||||
sub6.Close()
|
case <-ctx.Done():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
s.sendQuery(ctx, fqdn, clientIP, option)
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
for {
|
||||||
|
ips, err := s.findIPsForDomain(fqdn, option)
|
||||||
|
if err != errRecordNotFound {
|
||||||
|
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
|
||||||
|
return ips, err
|
||||||
|
}
|
||||||
|
|
||||||
ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
|
select {
|
||||||
log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
|
case <-ctx.Done():
|
||||||
return ips, ttl, err
|
return nil, ctx.Err()
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,13 +16,13 @@ import (
|
|||||||
func TestTCPLocalNameServer(t *testing.T) {
|
func TestTCPLocalNameServer(t *testing.T) {
|
||||||
url, err := url.Parse("tcp+local://8.8.8.8")
|
url, err := url.Parse("tcp+local://8.8.8.8")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
s, err := NewTCPLocalNameServer(url, false, net.IP(nil))
|
s, err := NewTCPLocalNameServer(url, QueryStrategy_USE_IP)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
})
|
}, false)
|
||||||
cancel()
|
cancel()
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if len(ips) == 0 {
|
if len(ips) == 0 {
|
||||||
@ -33,13 +33,13 @@ func TestTCPLocalNameServer(t *testing.T) {
|
|||||||
func TestTCPLocalNameServerWithCache(t *testing.T) {
|
func TestTCPLocalNameServerWithCache(t *testing.T) {
|
||||||
url, err := url.Parse("tcp+local://8.8.8.8")
|
url, err := url.Parse("tcp+local://8.8.8.8")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
s, err := NewTCPLocalNameServer(url, false, net.IP(nil))
|
s, err := NewTCPLocalNameServer(url, QueryStrategy_USE_IP)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
})
|
}, false)
|
||||||
cancel()
|
cancel()
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if len(ips) == 0 {
|
if len(ips) == 0 {
|
||||||
@ -47,10 +47,10 @@ func TestTCPLocalNameServerWithCache(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
ips2, _, err := s.QueryIP(ctx2, "google.com", dns_feature.IPOption{
|
ips2, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
})
|
}, true)
|
||||||
cancel()
|
cancel()
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
if r := cmp.Diff(ips2, ips); r != "" {
|
if r := cmp.Diff(ips2, ips); r != "" {
|
||||||
@ -61,13 +61,13 @@ func TestTCPLocalNameServerWithCache(t *testing.T) {
|
|||||||
func TestTCPLocalNameServerWithIPv4Override(t *testing.T) {
|
func TestTCPLocalNameServerWithIPv4Override(t *testing.T) {
|
||||||
url, err := url.Parse("tcp+local://8.8.8.8")
|
url, err := url.Parse("tcp+local://8.8.8.8")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
s, err := NewTCPLocalNameServer(url, false, net.IP(nil))
|
s, err := NewTCPLocalNameServer(url, QueryStrategy_USE_IP4)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: false,
|
IPv6Enable: true,
|
||||||
})
|
}, false)
|
||||||
cancel()
|
cancel()
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
@ -85,13 +85,13 @@ func TestTCPLocalNameServerWithIPv4Override(t *testing.T) {
|
|||||||
func TestTCPLocalNameServerWithIPv6Override(t *testing.T) {
|
func TestTCPLocalNameServerWithIPv6Override(t *testing.T) {
|
||||||
url, err := url.Parse("tcp+local://8.8.8.8")
|
url, err := url.Parse("tcp+local://8.8.8.8")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
s, err := NewTCPLocalNameServer(url, false, net.IP(nil))
|
s, err := NewTCPLocalNameServer(url, QueryStrategy_USE_IP6)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
|
||||||
IPv4Enable: false,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
})
|
}, false)
|
||||||
cancel()
|
cancel()
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
go_errors "errors"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@ -14,6 +13,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/protocol/dns"
|
"github.com/xtls/xray-core/common/protocol/dns"
|
||||||
udp_proto "github.com/xtls/xray-core/common/protocol/udp"
|
udp_proto "github.com/xtls/xray-core/common/protocol/udp"
|
||||||
|
"github.com/xtls/xray-core/common/signal/pubsub"
|
||||||
"github.com/xtls/xray-core/common/task"
|
"github.com/xtls/xray-core/common/task"
|
||||||
dns_feature "github.com/xtls/xray-core/features/dns"
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
"github.com/xtls/xray-core/features/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
@ -24,36 +24,35 @@ import (
|
|||||||
// ClassicNameServer implemented traditional UDP DNS.
|
// ClassicNameServer implemented traditional UDP DNS.
|
||||||
type ClassicNameServer struct {
|
type ClassicNameServer struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
cacheController *CacheController
|
name string
|
||||||
address *net.Destination
|
address *net.Destination
|
||||||
requests map[uint16]*udpDnsRequest
|
ips map[string]*record
|
||||||
|
requests map[uint16]*dnsRequest
|
||||||
|
pub *pubsub.Service
|
||||||
udpServer *udp.Dispatcher
|
udpServer *udp.Dispatcher
|
||||||
requestsCleanup *task.Periodic
|
cleanup *task.Periodic
|
||||||
reqID uint32
|
reqID uint32
|
||||||
clientIP net.IP
|
queryStrategy QueryStrategy
|
||||||
}
|
|
||||||
|
|
||||||
type udpDnsRequest struct {
|
|
||||||
dnsRequest
|
|
||||||
ctx context.Context
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClassicNameServer creates udp server object for remote resolving.
|
// NewClassicNameServer creates udp server object for remote resolving.
|
||||||
func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher, disableCache bool, clientIP net.IP) *ClassicNameServer {
|
func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher, queryStrategy QueryStrategy) *ClassicNameServer {
|
||||||
// default to 53 if unspecific
|
// default to 53 if unspecific
|
||||||
if address.Port == 0 {
|
if address.Port == 0 {
|
||||||
address.Port = net.Port(53)
|
address.Port = net.Port(53)
|
||||||
}
|
}
|
||||||
|
|
||||||
s := &ClassicNameServer{
|
s := &ClassicNameServer{
|
||||||
cacheController: NewCacheController(strings.ToUpper(address.String()), disableCache),
|
|
||||||
address: &address,
|
address: &address,
|
||||||
requests: make(map[uint16]*udpDnsRequest),
|
ips: make(map[string]*record),
|
||||||
clientIP: clientIP,
|
requests: make(map[uint16]*dnsRequest),
|
||||||
|
pub: pubsub.NewService(),
|
||||||
|
name: strings.ToUpper(address.String()),
|
||||||
|
queryStrategy: queryStrategy,
|
||||||
}
|
}
|
||||||
s.requestsCleanup = &task.Periodic{
|
s.cleanup = &task.Periodic{
|
||||||
Interval: time.Minute,
|
Interval: time.Minute,
|
||||||
Execute: s.RequestsCleanup,
|
Execute: s.Cleanup,
|
||||||
}
|
}
|
||||||
s.udpServer = udp.NewDispatcher(dispatcher, s.HandleResponse)
|
s.udpServer = udp.NewDispatcher(dispatcher, s.HandleResponse)
|
||||||
errors.LogInfo(context.Background(), "DNS: created UDP client initialized for ", address.NetAddr())
|
errors.LogInfo(context.Background(), "DNS: created UDP client initialized for ", address.NetAddr())
|
||||||
@ -62,17 +61,37 @@ func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher
|
|||||||
|
|
||||||
// Name implements Server.
|
// Name implements Server.
|
||||||
func (s *ClassicNameServer) Name() string {
|
func (s *ClassicNameServer) Name() string {
|
||||||
return s.cacheController.name
|
return s.name
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestsCleanup clears expired items from cache
|
// Cleanup clears expired items from cache
|
||||||
func (s *ClassicNameServer) RequestsCleanup() error {
|
func (s *ClassicNameServer) Cleanup() error {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
s.Lock()
|
s.Lock()
|
||||||
defer s.Unlock()
|
defer s.Unlock()
|
||||||
|
|
||||||
if len(s.requests) == 0 {
|
if len(s.ips) == 0 && len(s.requests) == 0 {
|
||||||
return errors.New(s.Name(), " nothing to do. stopping...")
|
return errors.New(s.name, " nothing to do. stopping...")
|
||||||
|
}
|
||||||
|
|
||||||
|
for domain, record := range s.ips {
|
||||||
|
if record.A != nil && record.A.Expire.Before(now) {
|
||||||
|
record.A = nil
|
||||||
|
}
|
||||||
|
if record.AAAA != nil && record.AAAA.Expire.Before(now) {
|
||||||
|
record.AAAA = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if record.A == nil && record.AAAA == nil {
|
||||||
|
errors.LogDebug(context.Background(), s.name, " cleanup ", domain)
|
||||||
|
delete(s.ips, domain)
|
||||||
|
} else {
|
||||||
|
s.ips[domain] = record
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s.ips) == 0 {
|
||||||
|
s.ips = make(map[string]*record)
|
||||||
}
|
}
|
||||||
|
|
||||||
for id, req := range s.requests {
|
for id, req := range s.requests {
|
||||||
@ -82,7 +101,7 @@ func (s *ClassicNameServer) RequestsCleanup() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(s.requests) == 0 {
|
if len(s.requests) == 0 {
|
||||||
s.requests = make(map[uint16]*udpDnsRequest)
|
s.requests = make(map[uint16]*dnsRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -92,7 +111,7 @@ func (s *ClassicNameServer) RequestsCleanup() error {
|
|||||||
func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_proto.Packet) {
|
func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_proto.Packet) {
|
||||||
ipRec, err := parseResponse(packet.Payload.Bytes())
|
ipRec, err := parseResponse(packet.Payload.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogError(ctx, s.Name(), " fail to parse responded DNS udp")
|
errors.LogError(ctx, s.name, " fail to parse responded DNS udp")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,107 +124,179 @@ func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_prot
|
|||||||
}
|
}
|
||||||
s.Unlock()
|
s.Unlock()
|
||||||
if !ok {
|
if !ok {
|
||||||
errors.LogError(ctx, s.Name(), " cannot find the pending request")
|
errors.LogError(ctx, s.name, " cannot find the pending request")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// if truncated, retry with EDNS0 option(udp payload size: 1350)
|
var rec record
|
||||||
if ipRec.RawHeader.Truncated {
|
switch req.reqType {
|
||||||
// if already has EDNS0 option, no need to retry
|
case dnsmessage.TypeA:
|
||||||
if len(req.msg.Additionals) == 0 {
|
rec.A = ipRec
|
||||||
// copy necessary meta data from original request
|
case dnsmessage.TypeAAAA:
|
||||||
// and add EDNS0 option
|
rec.AAAA = ipRec
|
||||||
opt := new(dnsmessage.Resource)
|
}
|
||||||
common.Must(opt.Header.SetEDNS0(1350, 0xfe00, true))
|
|
||||||
opt.Body = &dnsmessage.OPTResource{}
|
elapsed := time.Since(req.start)
|
||||||
newMsg := *req.msg
|
errors.LogInfo(ctx, s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed)
|
||||||
newReq := *req
|
if len(req.domain) > 0 && (rec.A != nil || rec.AAAA != nil) {
|
||||||
newMsg.Additionals = append(newMsg.Additionals, *opt)
|
s.updateIP(req.domain, &rec)
|
||||||
newMsg.ID = s.newReqID()
|
|
||||||
newReq.msg = &newMsg
|
|
||||||
s.addPendingRequest(&newReq)
|
|
||||||
b, _ := dns.PackMessage(newReq.msg)
|
|
||||||
s.udpServer.Dispatch(toDnsContext(newReq.ctx, s.address.String()), *s.address, b)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.cacheController.updateIP(&req.dnsRequest, ipRec)
|
func (s *ClassicNameServer) updateIP(domain string, newRec *record) {
|
||||||
|
s.Lock()
|
||||||
|
|
||||||
|
rec, found := s.ips[domain]
|
||||||
|
if !found {
|
||||||
|
rec = &record{}
|
||||||
|
}
|
||||||
|
|
||||||
|
updated := false
|
||||||
|
if isNewer(rec.A, newRec.A) {
|
||||||
|
rec.A = newRec.A
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
if isNewer(rec.AAAA, newRec.AAAA) {
|
||||||
|
rec.AAAA = newRec.AAAA
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if updated {
|
||||||
|
errors.LogDebug(context.Background(), s.name, " updating IP records for domain:", domain)
|
||||||
|
s.ips[domain] = rec
|
||||||
|
}
|
||||||
|
if newRec.A != nil {
|
||||||
|
s.pub.Publish(domain+"4", nil)
|
||||||
|
}
|
||||||
|
if newRec.AAAA != nil {
|
||||||
|
s.pub.Publish(domain+"6", nil)
|
||||||
|
}
|
||||||
|
s.Unlock()
|
||||||
|
common.Must(s.cleanup.Start())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ClassicNameServer) newReqID() uint16 {
|
func (s *ClassicNameServer) newReqID() uint16 {
|
||||||
return uint16(atomic.AddUint32(&s.reqID, 1))
|
return uint16(atomic.AddUint32(&s.reqID, 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ClassicNameServer) addPendingRequest(req *udpDnsRequest) {
|
func (s *ClassicNameServer) addPendingRequest(req *dnsRequest) {
|
||||||
s.Lock()
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
|
||||||
id := req.msg.ID
|
id := req.msg.ID
|
||||||
req.expire = time.Now().Add(time.Second * 8)
|
req.expire = time.Now().Add(time.Second * 8)
|
||||||
s.requests[id] = req
|
s.requests[id] = req
|
||||||
s.Unlock()
|
|
||||||
common.Must(s.requestsCleanup.Start())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ClassicNameServer) sendQuery(ctx context.Context, _ chan<- error, domain string, option dns_feature.IPOption) {
|
func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
|
||||||
errors.LogDebug(ctx, s.Name(), " querying DNS for: ", domain)
|
errors.LogDebug(ctx, s.name, " querying DNS for: ", domain)
|
||||||
|
|
||||||
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
|
||||||
|
|
||||||
for _, req := range reqs {
|
for _, req := range reqs {
|
||||||
udpReq := &udpDnsRequest{
|
s.addPendingRequest(req)
|
||||||
dnsRequest: *req,
|
|
||||||
ctx: ctx,
|
|
||||||
}
|
|
||||||
s.addPendingRequest(udpReq)
|
|
||||||
b, _ := dns.PackMessage(req.msg)
|
b, _ := dns.PackMessage(req.msg)
|
||||||
s.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b)
|
s.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ClassicNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
|
||||||
|
s.RLock()
|
||||||
|
record, found := s.ips[domain]
|
||||||
|
s.RUnlock()
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil, errRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
var err4 error
|
||||||
|
var err6 error
|
||||||
|
var ips []net.Address
|
||||||
|
var ip6 []net.Address
|
||||||
|
|
||||||
|
if option.IPv4Enable {
|
||||||
|
ips, err4 = record.A.getIPs()
|
||||||
|
}
|
||||||
|
|
||||||
|
if option.IPv6Enable {
|
||||||
|
ip6, err6 = record.AAAA.getIPs()
|
||||||
|
ips = append(ips, ip6...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ips) > 0 {
|
||||||
|
return toNetIP(ips)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err4 != nil {
|
||||||
|
return nil, err4
|
||||||
|
}
|
||||||
|
|
||||||
|
if err6 != nil {
|
||||||
|
return nil, err6
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, dns_feature.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
|
||||||
// QueryIP implements Server.
|
// QueryIP implements Server.
|
||||||
func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
|
func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) {
|
||||||
fqdn := Fqdn(domain)
|
fqdn := Fqdn(domain)
|
||||||
sub4, sub6 := s.cacheController.registerSubscribers(fqdn, option)
|
option = ResolveIpOptionOverride(s.queryStrategy, option)
|
||||||
defer closeSubscribers(sub4, sub6)
|
if !option.IPv4Enable && !option.IPv6Enable {
|
||||||
|
return nil, dns_feature.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
|
||||||
if s.cacheController.disableCache {
|
if disableCache {
|
||||||
errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.Name())
|
errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.name)
|
||||||
} else {
|
} else {
|
||||||
ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
|
ips, err := s.findIPsForDomain(fqdn, option)
|
||||||
if !go_errors.Is(err, errRecordNotFound) {
|
if err == nil || err == dns_feature.ErrEmptyResponse {
|
||||||
errors.LogDebugInner(ctx, err, s.Name(), " cache HIT ", domain, " -> ", ips)
|
errors.LogDebugInner(ctx, err, s.name, " cache HIT ", domain, " -> ", ips)
|
||||||
log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
|
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
|
||||||
return ips, ttl, err
|
return ips, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
noResponseErrCh := make(chan error, 2)
|
// ipv4 and ipv6 belong to different subscription groups
|
||||||
s.sendQuery(ctx, noResponseErrCh, fqdn, option)
|
var sub4, sub6 *pubsub.Subscriber
|
||||||
start := time.Now()
|
if option.IPv4Enable {
|
||||||
|
sub4 = s.pub.Subscribe(fqdn + "4")
|
||||||
|
defer sub4.Close()
|
||||||
|
}
|
||||||
|
if option.IPv6Enable {
|
||||||
|
sub6 = s.pub.Subscribe(fqdn + "6")
|
||||||
|
defer sub6.Close()
|
||||||
|
}
|
||||||
|
done := make(chan interface{})
|
||||||
|
go func() {
|
||||||
if sub4 != nil {
|
if sub4 != nil {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, 0, ctx.Err()
|
|
||||||
case err := <-noResponseErrCh:
|
|
||||||
return nil, 0, err
|
|
||||||
case <-sub4.Wait():
|
case <-sub4.Wait():
|
||||||
sub4.Close()
|
case <-ctx.Done():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if sub6 != nil {
|
if sub6 != nil {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, 0, ctx.Err()
|
|
||||||
case err := <-noResponseErrCh:
|
|
||||||
return nil, 0, err
|
|
||||||
case <-sub6.Wait():
|
case <-sub6.Wait():
|
||||||
sub6.Close()
|
case <-ctx.Done():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
s.sendQuery(ctx, fqdn, clientIP, option)
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
for {
|
||||||
|
ips, err := s.findIPsForDomain(fqdn, option)
|
||||||
|
if err != errRecordNotFound {
|
||||||
|
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
|
||||||
|
return ips, err
|
||||||
|
}
|
||||||
|
|
||||||
ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
|
select {
|
||||||
log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
|
case <-ctx.Done():
|
||||||
return ips, ttl, err
|
return nil, ctx.Err()
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -324,7 +324,6 @@ func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest
|
|||||||
if w.sniffingConfig != nil {
|
if w.sniffingConfig != nil {
|
||||||
content.SniffingRequest.Enabled = w.sniffingConfig.Enabled
|
content.SniffingRequest.Enabled = w.sniffingConfig.Enabled
|
||||||
content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride
|
content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride
|
||||||
content.SniffingRequest.ExcludeForDomain = w.sniffingConfig.DomainsExcluded
|
|
||||||
content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly
|
content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly
|
||||||
content.SniffingRequest.RouteOnly = w.sniffingConfig.RouteOnly
|
content.SniffingRequest.RouteOnly = w.sniffingConfig.RouteOnly
|
||||||
}
|
}
|
||||||
|
@ -241,9 +241,7 @@ func (h *Handler) DestIpAddress() net.IP {
|
|||||||
// Dial implements internet.Dialer.
|
// Dial implements internet.Dialer.
|
||||||
func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) {
|
func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) {
|
||||||
if h.senderSettings != nil {
|
if h.senderSettings != nil {
|
||||||
|
|
||||||
if h.senderSettings.ProxySettings.HasTag() {
|
if h.senderSettings.ProxySettings.HasTag() {
|
||||||
|
|
||||||
tag := h.senderSettings.ProxySettings.Tag
|
tag := h.senderSettings.ProxySettings.Tag
|
||||||
handler := h.outboundManager.GetHandler(tag)
|
handler := h.outboundManager.GetHandler(tag)
|
||||||
if handler != nil {
|
if handler != nil {
|
||||||
@ -272,40 +270,22 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connecti
|
|||||||
}
|
}
|
||||||
|
|
||||||
if h.senderSettings.Via != nil {
|
if h.senderSettings.Via != nil {
|
||||||
|
|
||||||
outbounds := session.OutboundsFromContext(ctx)
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
ob := outbounds[len(outbounds)-1]
|
ob := outbounds[len(outbounds)-1]
|
||||||
addr := h.senderSettings.Via.AsAddress()
|
if h.senderSettings.ViaCidr == "" {
|
||||||
var domain string
|
if h.senderSettings.Via.AsAddress().Family().IsDomain() && h.senderSettings.Via.AsAddress().Domain() == "origin" {
|
||||||
if addr.Family().IsDomain() {
|
|
||||||
domain = addr.Domain()
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case h.senderSettings.ViaCidr != "":
|
|
||||||
ob.Gateway = ParseRandomIP(addr, h.senderSettings.ViaCidr)
|
|
||||||
|
|
||||||
case domain == "origin":
|
|
||||||
|
|
||||||
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
||||||
origin, _, err := net.SplitHostPort(inbound.Conn.LocalAddr().String())
|
origin, _, err := net.SplitHostPort(inbound.Conn.LocalAddr().String())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ob.Gateway = net.ParseAddress(origin)
|
ob.Gateway = net.ParseAddress(origin)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
case domain == "srcip":
|
} else {
|
||||||
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
ob.Gateway = h.senderSettings.Via.AsAddress()
|
||||||
srcip, _, err := net.SplitHostPort(inbound.Conn.RemoteAddr().String())
|
|
||||||
if err == nil {
|
|
||||||
ob.Gateway = net.ParseAddress(srcip)
|
|
||||||
}
|
}
|
||||||
|
} else { //Get a random address.
|
||||||
|
ob.Gateway = ParseRandomIPv6(h.senderSettings.Via.AsAddress(), h.senderSettings.ViaCidr)
|
||||||
}
|
}
|
||||||
//case addr.Family().IsDomain():
|
|
||||||
default:
|
|
||||||
ob.Gateway = addr
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,25 +325,23 @@ func (h *Handler) Start() error {
|
|||||||
// Close implements common.Closable.
|
// Close implements common.Closable.
|
||||||
func (h *Handler) Close() error {
|
func (h *Handler) Close() error {
|
||||||
common.Close(h.mux)
|
common.Close(h.mux)
|
||||||
common.Close(h.proxy)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseRandomIP(addr net.Address, prefix string) net.Address {
|
func ParseRandomIPv6(address net.Address, prefix string) net.Address {
|
||||||
|
_, network, _ := gonet.ParseCIDR(address.IP().String() + "/" + prefix)
|
||||||
|
|
||||||
_, ipnet, _ := gonet.ParseCIDR(addr.IP().String() + "/" + prefix)
|
maskSize, totalBits := network.Mask.Size()
|
||||||
|
subnetSize := big.NewInt(1).Lsh(big.NewInt(1), uint(totalBits-maskSize))
|
||||||
|
|
||||||
ones, bits := ipnet.Mask.Size()
|
// random
|
||||||
subnetSize := new(big.Int).Lsh(big.NewInt(1), uint(bits-ones))
|
randomBigInt, _ := rand.Int(rand.Reader, subnetSize)
|
||||||
|
|
||||||
rnd, _ := rand.Int(rand.Reader, subnetSize)
|
startIPBigInt := big.NewInt(0).SetBytes(network.IP.To16())
|
||||||
|
randomIPBigInt := big.NewInt(0).Add(startIPBigInt, randomBigInt)
|
||||||
|
|
||||||
startInt := new(big.Int).SetBytes(ipnet.IP)
|
randomIPBytes := randomIPBigInt.Bytes()
|
||||||
rndInt := new(big.Int).Add(startInt, rnd)
|
randomIPBytes = append(make([]byte, 16-len(randomIPBytes)), randomIPBytes...)
|
||||||
|
|
||||||
rndBytes := rndInt.Bytes()
|
return net.ParseAddress(gonet.IP(randomIPBytes).String())
|
||||||
padded := make([]byte, len(ipnet.IP))
|
|
||||||
copy(padded[len(padded)-len(rndBytes):], rndBytes)
|
|
||||||
|
|
||||||
return net.ParseAddress(gonet.IP(padded).String())
|
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ type MultiGeoIPMatcher struct {
|
|||||||
func NewMultiGeoIPMatcher(geoips []*GeoIP, onSource bool) (*MultiGeoIPMatcher, error) {
|
func NewMultiGeoIPMatcher(geoips []*GeoIP, onSource bool) (*MultiGeoIPMatcher, error) {
|
||||||
var matchers []*GeoIPMatcher
|
var matchers []*GeoIPMatcher
|
||||||
for _, geoip := range geoips {
|
for _, geoip := range geoips {
|
||||||
matcher, err := GlobalGeoIPContainer.Add(geoip)
|
matcher, err := globalGeoIPContainer.Add(geoip)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -115,4 +115,4 @@ func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var GlobalGeoIPContainer GeoIPMatcherContainer
|
var globalGeoIPContainer GeoIPMatcherContainer
|
||||||
|
@ -177,7 +177,7 @@ func TestIPOnDemand(t *testing.T) {
|
|||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
}).Return([]net.IP{{192, 168, 0, 1}}, uint32(600), nil).AnyTimes()
|
}).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes()
|
||||||
|
|
||||||
r := new(Router)
|
r := new(Router)
|
||||||
common.Must(r.Init(context.TODO(), config, mockDNS, nil, nil))
|
common.Must(r.Init(context.TODO(), config, mockDNS, nil, nil))
|
||||||
@ -222,7 +222,7 @@ func TestIPIfNonMatchDomain(t *testing.T) {
|
|||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
}).Return([]net.IP{{192, 168, 0, 1}}, uint32(600), nil).AnyTimes()
|
}).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes()
|
||||||
|
|
||||||
r := new(Router)
|
r := new(Router)
|
||||||
common.Must(r.Init(context.TODO(), config, mockDNS, nil, nil))
|
common.Must(r.Init(context.TODO(), config, mockDNS, nil, nil))
|
||||||
|
@ -40,11 +40,11 @@ func (c *OnlineMap) AddIP(ip string) {
|
|||||||
if ip == "127.0.0.1" {
|
if ip == "127.0.0.1" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.access.Lock()
|
|
||||||
if _, ok := list[ip]; !ok {
|
if _, ok := list[ip]; !ok {
|
||||||
|
c.access.Lock()
|
||||||
list[ip] = time.Now()
|
list[ip] = time.Now()
|
||||||
}
|
|
||||||
c.access.Unlock()
|
c.access.Unlock()
|
||||||
|
}
|
||||||
if time.Since(c.lastCleanup) > c.cleanupPeriod {
|
if time.Since(c.lastCleanup) > c.cleanupPeriod {
|
||||||
list = c.RemoveExpiredIPs(list)
|
list = c.RemoveExpiredIPs(list)
|
||||||
c.lastCleanup = time.Now()
|
c.lastCleanup = time.Now()
|
||||||
|
@ -13,19 +13,8 @@ const (
|
|||||||
Size = 8192
|
Size = 8192
|
||||||
)
|
)
|
||||||
|
|
||||||
var zero = [Size * 10]byte{0}
|
|
||||||
|
|
||||||
var pool = bytespool.GetPool(Size)
|
var pool = bytespool.GetPool(Size)
|
||||||
|
|
||||||
// ownership represents the data owner of the buffer.
|
|
||||||
type ownership uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
managed ownership = iota
|
|
||||||
unmanaged
|
|
||||||
bytespools
|
|
||||||
)
|
|
||||||
|
|
||||||
// Buffer is a recyclable allocation of a byte array. Buffer.Release() recycles
|
// Buffer is a recyclable allocation of a byte array. Buffer.Release() recycles
|
||||||
// the buffer into an internal buffer pool, in order to recreate a buffer more
|
// the buffer into an internal buffer pool, in order to recreate a buffer more
|
||||||
// quickly.
|
// quickly.
|
||||||
@ -33,11 +22,11 @@ type Buffer struct {
|
|||||||
v []byte
|
v []byte
|
||||||
start int32
|
start int32
|
||||||
end int32
|
end int32
|
||||||
ownership ownership
|
unmanaged bool
|
||||||
UDP *net.Destination
|
UDP *net.Destination
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a Buffer with 0 length and 8K capacity, managed.
|
// New creates a Buffer with 0 length and 8K capacity.
|
||||||
func New() *Buffer {
|
func New() *Buffer {
|
||||||
buf := pool.Get().([]byte)
|
buf := pool.Get().([]byte)
|
||||||
if cap(buf) >= Size {
|
if cap(buf) >= Size {
|
||||||
@ -51,7 +40,7 @@ func New() *Buffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewExisted creates a standard size Buffer with an existed bytearray, managed.
|
// NewExisted creates a managed, standard size Buffer with an existed bytearray
|
||||||
func NewExisted(b []byte) *Buffer {
|
func NewExisted(b []byte) *Buffer {
|
||||||
if cap(b) < Size {
|
if cap(b) < Size {
|
||||||
panic("Invalid buffer")
|
panic("Invalid buffer")
|
||||||
@ -68,16 +57,16 @@ func NewExisted(b []byte) *Buffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromBytes creates a Buffer with an existed bytearray, unmanaged.
|
// FromBytes creates a Buffer with an existed bytearray
|
||||||
func FromBytes(b []byte) *Buffer {
|
func FromBytes(b []byte) *Buffer {
|
||||||
return &Buffer{
|
return &Buffer{
|
||||||
v: b,
|
v: b,
|
||||||
end: int32(len(b)),
|
end: int32(len(b)),
|
||||||
ownership: unmanaged,
|
unmanaged: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// StackNew creates a new Buffer object on stack, managed.
|
// StackNew creates a new Buffer object on stack.
|
||||||
// This method is for buffers that is released in the same function.
|
// This method is for buffers that is released in the same function.
|
||||||
func StackNew() Buffer {
|
func StackNew() Buffer {
|
||||||
buf := pool.Get().([]byte)
|
buf := pool.Get().([]byte)
|
||||||
@ -92,17 +81,9 @@ func StackNew() Buffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWithSize creates a Buffer with 0 length and capacity with at least the given size, bytespool's.
|
|
||||||
func NewWithSize(size int32) *Buffer {
|
|
||||||
return &Buffer{
|
|
||||||
v: bytespool.Alloc(size),
|
|
||||||
ownership: bytespools,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release recycles the buffer into an internal buffer pool.
|
// Release recycles the buffer into an internal buffer pool.
|
||||||
func (b *Buffer) Release() {
|
func (b *Buffer) Release() {
|
||||||
if b == nil || b.v == nil || b.ownership == unmanaged {
|
if b == nil || b.v == nil || b.unmanaged {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,14 +91,9 @@ func (b *Buffer) Release() {
|
|||||||
b.v = nil
|
b.v = nil
|
||||||
b.Clear()
|
b.Clear()
|
||||||
|
|
||||||
switch b.ownership {
|
|
||||||
case managed:
|
|
||||||
if cap(p) == Size {
|
if cap(p) == Size {
|
||||||
pool.Put(p)
|
pool.Put(p)
|
||||||
}
|
}
|
||||||
case bytespools:
|
|
||||||
bytespool.Free(p)
|
|
||||||
}
|
|
||||||
b.UDP = nil
|
b.UDP = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,7 +128,6 @@ func (b *Buffer) Extend(n int32) []byte {
|
|||||||
}
|
}
|
||||||
ext := b.v[b.end:end]
|
ext := b.v[b.end:end]
|
||||||
b.end = end
|
b.end = end
|
||||||
copy(ext, zero[:])
|
|
||||||
return ext
|
return ext
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +176,6 @@ func (b *Buffer) Check() {
|
|||||||
|
|
||||||
// Resize cuts the buffer at the given position.
|
// Resize cuts the buffer at the given position.
|
||||||
func (b *Buffer) Resize(from, to int32) {
|
func (b *Buffer) Resize(from, to int32) {
|
||||||
oldEnd := b.end
|
|
||||||
if from < 0 {
|
if from < 0 {
|
||||||
from += b.Len()
|
from += b.Len()
|
||||||
}
|
}
|
||||||
@ -214,9 +188,6 @@ func (b *Buffer) Resize(from, to int32) {
|
|||||||
b.end = b.start + to
|
b.end = b.start + to
|
||||||
b.start += from
|
b.start += from
|
||||||
b.Check()
|
b.Check()
|
||||||
if b.end > oldEnd {
|
|
||||||
copy(b.v[oldEnd:b.end], zero[:])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Advance cuts the buffer at the given position.
|
// Advance cuts the buffer at the given position.
|
||||||
@ -244,6 +215,13 @@ func (b *Buffer) Cap() int32 {
|
|||||||
return int32(len(b.v))
|
return int32(len(b.v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewWithSize creates a Buffer with 0 length and capacity with at least the given size.
|
||||||
|
func NewWithSize(size int32) *Buffer {
|
||||||
|
return &Buffer{
|
||||||
|
v: bytespool.Alloc(size),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// IsEmpty returns true if the buffer is empty.
|
// IsEmpty returns true if the buffer is empty.
|
||||||
func (b *Buffer) IsEmpty() bool {
|
func (b *Buffer) IsEmpty() bool {
|
||||||
return b.Len() == 0
|
return b.Len() == 0
|
||||||
|
@ -1,15 +1,2 @@
|
|||||||
// Package crypto provides common crypto libraries for Xray.
|
// Package crypto provides common crypto libraries for Xray.
|
||||||
package crypto // import "github.com/xtls/xray-core/common/crypto"
|
package crypto // import "github.com/xtls/xray-core/common/crypto"
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RandBetween(from int64, to int64) int64 {
|
|
||||||
if from == to {
|
|
||||||
return from
|
|
||||||
}
|
|
||||||
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
|
|
||||||
return from + bigInt.Int64()
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package errors
|
package errors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,12 +36,12 @@ func AllEqual(expected error, actual error) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, err := range errs {
|
for _, err := range errs {
|
||||||
if !errors.Is(err, expected) {
|
if err != expected {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return errors.Is(errs, expected)
|
return errs == expected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,7 +120,7 @@ func (w *ServerWorker) handleStatusKeepAlive(meta *FrameMetadata, reader *buf.Bu
|
|||||||
func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata, reader *buf.BufferedReader) error {
|
func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata, reader *buf.BufferedReader) error {
|
||||||
// deep-clone outbounds because it is going to be mutated concurrently
|
// deep-clone outbounds because it is going to be mutated concurrently
|
||||||
// (Target and OriginalTarget)
|
// (Target and OriginalTarget)
|
||||||
ctx = session.ContextCloneOutboundsAndContent(ctx)
|
ctx = session.ContextCloneOutbounds(ctx)
|
||||||
errors.LogInfo(ctx, "received request for ", meta.Target)
|
errors.LogInfo(ctx, "received request for ", meta.Target)
|
||||||
{
|
{
|
||||||
msg := &log.AccessMessage{
|
msg := &log.AccessMessage{
|
||||||
|
@ -1,14 +1,2 @@
|
|||||||
// Package net is a drop-in replacement to Golang's net package, with some more functionalities.
|
// Package net is a drop-in replacement to Golang's net package, with some more functionalities.
|
||||||
package net // import "github.com/xtls/xray-core/common/net"
|
package net // import "github.com/xtls/xray-core/common/net"
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// defines the maximum time an idle TCP session can survive in the tunnel, so
|
|
||||||
// it should be consistent across HTTP versions and with other transports.
|
|
||||||
const ConnIdleTimeout = 300 * time.Second
|
|
||||||
|
|
||||||
// consistent with quic-go
|
|
||||||
const QuicgoH3KeepAlivePeriod = 10 * time.Second
|
|
||||||
|
|
||||||
// consistent with chrome
|
|
||||||
const ChromeH2KeepAlivePeriod = 45 * time.Second
|
|
||||||
|
@ -3,7 +3,6 @@ package filesystem
|
|||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
"github.com/xtls/xray-core/common/platform"
|
"github.com/xtls/xray-core/common/platform"
|
||||||
@ -29,13 +28,6 @@ func ReadAsset(file string) ([]byte, error) {
|
|||||||
return ReadFile(platform.GetAssetLocation(file))
|
return ReadFile(platform.GetAssetLocation(file))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadCert(file string) ([]byte, error) {
|
|
||||||
if filepath.IsAbs(file) {
|
|
||||||
return ReadFile(file)
|
|
||||||
}
|
|
||||||
return ReadFile(platform.GetCertLocation(file))
|
|
||||||
}
|
|
||||||
|
|
||||||
func CopyFile(dst string, src string) error {
|
func CopyFile(dst string, src string) error {
|
||||||
bytes, err := ReadFile(src)
|
bytes, err := ReadFile(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -21,7 +21,7 @@ func GetToolLocation(file string) string {
|
|||||||
return filepath.Join(toolPath, file)
|
return filepath.Join(toolPath, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAssetLocation searches for `file` in the env dir, the executable dir, and certain locations
|
// GetAssetLocation searches for `file` in certain locations
|
||||||
func GetAssetLocation(file string) string {
|
func GetAssetLocation(file string) string {
|
||||||
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)
|
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)
|
||||||
defPath := filepath.Join(assetPath, file)
|
defPath := filepath.Join(assetPath, file)
|
||||||
@ -42,9 +42,3 @@ func GetAssetLocation(file string) string {
|
|||||||
// asset not found, let the caller throw out the error
|
// asset not found, let the caller throw out the error
|
||||||
return defPath
|
return defPath
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCertLocation searches for `file` in the env dir and the executable dir
|
|
||||||
func GetCertLocation(file string) string {
|
|
||||||
certPath := NewEnvFlag(CertLocation).GetValue(getExecutableDir)
|
|
||||||
return filepath.Join(certPath, file)
|
|
||||||
}
|
|
||||||
|
@ -13,7 +13,6 @@ const (
|
|||||||
ConfdirLocation = "xray.location.confdir"
|
ConfdirLocation = "xray.location.confdir"
|
||||||
ToolLocation = "xray.location.tool"
|
ToolLocation = "xray.location.tool"
|
||||||
AssetLocation = "xray.location.asset"
|
AssetLocation = "xray.location.asset"
|
||||||
CertLocation = "xray.location.cert"
|
|
||||||
|
|
||||||
UseReadV = "xray.buf.readv"
|
UseReadV = "xray.buf.readv"
|
||||||
UseFreedomSplice = "xray.buf.splice"
|
UseFreedomSplice = "xray.buf.splice"
|
||||||
|
@ -19,14 +19,8 @@ func GetToolLocation(file string) string {
|
|||||||
return filepath.Join(toolPath, file+".exe")
|
return filepath.Join(toolPath, file+".exe")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAssetLocation searches for `file` in the env dir and the executable dir
|
// GetAssetLocation searches for `file` in the executable dir
|
||||||
func GetAssetLocation(file string) string {
|
func GetAssetLocation(file string) string {
|
||||||
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)
|
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)
|
||||||
return filepath.Join(assetPath, file)
|
return filepath.Join(assetPath, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCertLocation searches for `file` in the env dir and the executable dir
|
|
||||||
func GetCertLocation(file string) string {
|
|
||||||
certPath := NewEnvFlag(CertLocation).GetValue(getExecutableDir)
|
|
||||||
return filepath.Join(certPath, file)
|
|
||||||
}
|
|
||||||
|
@ -63,7 +63,7 @@ func SniffHTTP(b []byte, c context.Context) (*SniffHeader, error) {
|
|||||||
ShouldSniffAttr := true
|
ShouldSniffAttr := true
|
||||||
// If content.Attributes have information, that means it comes from HTTP inbound PlainHTTP mode.
|
// If content.Attributes have information, that means it comes from HTTP inbound PlainHTTP mode.
|
||||||
// It will set attributes, so skip it.
|
// It will set attributes, so skip it.
|
||||||
if content == nil || len(content.Attributes) != 0 {
|
if content == nil || content.AttributeLen() != 0 {
|
||||||
ShouldSniffAttr = false
|
ShouldSniffAttr = false
|
||||||
}
|
}
|
||||||
if err := beginWithHTTPMethod(b); err != nil {
|
if err := beginWithHTTPMethod(b); err != nil {
|
||||||
|
@ -1,7 +1 @@
|
|||||||
package protocol // import "github.com/xtls/xray-core/common/protocol"
|
package protocol // import "github.com/xtls/xray-core/common/protocol"
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ErrProtoNeedMoreData = errors.New("protocol matches, but need more data to complete sniffing")
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package quic
|
package quic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
@ -10,8 +11,8 @@ import (
|
|||||||
"github.com/quic-go/quic-go/quicvarint"
|
"github.com/quic-go/quic-go/quicvarint"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
|
"github.com/xtls/xray-core/common/bytespool"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
|
||||||
ptls "github.com/xtls/xray-core/common/protocol/tls"
|
ptls "github.com/xtls/xray-core/common/protocol/tls"
|
||||||
"golang.org/x/crypto/hkdf"
|
"golang.org/x/crypto/hkdf"
|
||||||
)
|
)
|
||||||
@ -46,17 +47,22 @@ var (
|
|||||||
errNotQuicInitial = errors.New("not initial packet")
|
errNotQuicInitial = errors.New("not initial packet")
|
||||||
)
|
)
|
||||||
|
|
||||||
func SniffQUIC(b []byte) (*SniffHeader, error) {
|
func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) {
|
||||||
if len(b) == 0 {
|
// In extremely rare cases, this sniffer may cause slice error
|
||||||
return nil, common.ErrNoClue
|
// and we set recover() here to prevent crash.
|
||||||
|
// TODO: Thoroughly fix this panic
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
errors.LogError(context.Background(), "Failed to sniff QUIC: ", r)
|
||||||
|
resultReturn = nil
|
||||||
|
errorReturn = common.ErrNoClue
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Crypto data separated across packets
|
// Crypto data separated across packets
|
||||||
cryptoLen := int32(0)
|
cryptoLen := 0
|
||||||
cryptoDataBuf := buf.NewWithSize(32767)
|
cryptoData := bytespool.Alloc(int32(len(b)))
|
||||||
defer cryptoDataBuf.Release()
|
defer bytespool.Free(cryptoData)
|
||||||
cache := buf.New()
|
|
||||||
defer cache.Release()
|
|
||||||
|
|
||||||
// Parse QUIC packets
|
// Parse QUIC packets
|
||||||
for len(b) > 0 {
|
for len(b) > 0 {
|
||||||
@ -99,7 +105,6 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
return nil, errNotQuic
|
return nil, errNotQuic
|
||||||
}
|
}
|
||||||
|
|
||||||
if isQuicInitial { // Only initial packets have token, see https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.2
|
|
||||||
tokenLen, err := quicvarint.Read(buffer)
|
tokenLen, err := quicvarint.Read(buffer)
|
||||||
if err != nil || tokenLen > uint64(len(b)) {
|
if err != nil || tokenLen > uint64(len(b)) {
|
||||||
return nil, errNotQuic
|
return nil, errNotQuic
|
||||||
@ -108,16 +113,11 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
if _, err = buffer.ReadBytes(int32(tokenLen)); err != nil {
|
if _, err = buffer.ReadBytes(int32(tokenLen)); err != nil {
|
||||||
return nil, errNotQuic
|
return nil, errNotQuic
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
packetLen, err := quicvarint.Read(buffer)
|
packetLen, err := quicvarint.Read(buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errNotQuic
|
return nil, errNotQuic
|
||||||
}
|
}
|
||||||
// packetLen is impossible to be shorter than this
|
|
||||||
if packetLen < 4 {
|
|
||||||
return nil, errNotQuic
|
|
||||||
}
|
|
||||||
|
|
||||||
hdrLen := len(b) - int(buffer.Len())
|
hdrLen := len(b) - int(buffer.Len())
|
||||||
if len(b) < hdrLen+int(packetLen) {
|
if len(b) < hdrLen+int(packetLen) {
|
||||||
@ -130,6 +130,9 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
origPNBytes := make([]byte, 4)
|
||||||
|
copy(origPNBytes, b[hdrLen:hdrLen+4])
|
||||||
|
|
||||||
var salt []byte
|
var salt []byte
|
||||||
if versionNumber == version1 {
|
if versionNumber == version1 {
|
||||||
salt = quicSalt
|
salt = quicSalt
|
||||||
@ -144,34 +147,44 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.Clear()
|
cache := buf.New()
|
||||||
|
defer cache.Release()
|
||||||
|
|
||||||
mask := cache.Extend(int32(block.BlockSize()))
|
mask := cache.Extend(int32(block.BlockSize()))
|
||||||
block.Encrypt(mask, b[hdrLen+4:hdrLen+4+len(mask)])
|
block.Encrypt(mask, b[hdrLen+4:hdrLen+4+16])
|
||||||
b[0] ^= mask[0] & 0xf
|
b[0] ^= mask[0] & 0xf
|
||||||
packetNumberLength := int(b[0]&0x3 + 1)
|
for i := range b[hdrLen : hdrLen+4] {
|
||||||
for i := range packetNumberLength {
|
|
||||||
b[hdrLen+i] ^= mask[i+1]
|
b[hdrLen+i] ^= mask[i+1]
|
||||||
}
|
}
|
||||||
|
packetNumberLength := b[0]&0x3 + 1
|
||||||
|
if packetNumberLength != 1 {
|
||||||
|
return nil, errNotQuicInitial
|
||||||
|
}
|
||||||
|
var packetNumber uint32
|
||||||
|
{
|
||||||
|
n, err := buffer.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
packetNumber = uint32(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
extHdrLen := hdrLen + int(packetNumberLength)
|
||||||
|
copy(b[extHdrLen:hdrLen+4], origPNBytes[packetNumberLength:])
|
||||||
|
data := b[extHdrLen : int(packetLen)+hdrLen]
|
||||||
|
|
||||||
key := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic key", 16)
|
key := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic key", 16)
|
||||||
iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12)
|
iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12)
|
||||||
cipher := AEADAESGCMTLS13(key, iv)
|
cipher := AEADAESGCMTLS13(key, iv)
|
||||||
|
|
||||||
nonce := cache.Extend(int32(cipher.NonceSize()))
|
nonce := cache.Extend(int32(cipher.NonceSize()))
|
||||||
_, err = buffer.Read(nonce[len(nonce)-packetNumberLength:])
|
binary.BigEndian.PutUint64(nonce[len(nonce)-8:], uint64(packetNumber))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
extHdrLen := hdrLen + packetNumberLength
|
|
||||||
data := b[extHdrLen : int(packetLen)+hdrLen]
|
|
||||||
decrypted, err := cipher.Open(b[extHdrLen:extHdrLen], nonce, data, b[:extHdrLen])
|
decrypted, err := cipher.Open(b[extHdrLen:extHdrLen], nonce, data, b[:extHdrLen])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
buffer = buf.FromBytes(decrypted)
|
buffer = buf.FromBytes(decrypted)
|
||||||
for !buffer.IsEmpty() {
|
for i := 0; !buffer.IsEmpty(); i++ {
|
||||||
frameType, _ := buffer.ReadByte()
|
frameType := byte(0x0) // Default to PADDING frame
|
||||||
for frameType == 0x0 && !buffer.IsEmpty() {
|
for frameType == 0x0 && !buffer.IsEmpty() {
|
||||||
frameType, _ = buffer.ReadByte()
|
frameType, _ = buffer.ReadByte()
|
||||||
}
|
}
|
||||||
@ -220,15 +233,16 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
if err != nil || length > uint64(buffer.Len()) {
|
if err != nil || length > uint64(buffer.Len()) {
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
currentCryptoLen := int32(offset + length)
|
if cryptoLen < int(offset+length) {
|
||||||
if cryptoLen < currentCryptoLen {
|
cryptoLen = int(offset + length)
|
||||||
if cryptoDataBuf.Cap() < currentCryptoLen {
|
if len(cryptoData) < cryptoLen {
|
||||||
return nil, io.ErrShortBuffer
|
newCryptoData := bytespool.Alloc(int32(cryptoLen))
|
||||||
|
copy(newCryptoData, cryptoData)
|
||||||
|
bytespool.Free(cryptoData)
|
||||||
|
cryptoData = newCryptoData
|
||||||
}
|
}
|
||||||
cryptoDataBuf.Extend(currentCryptoLen - cryptoLen)
|
|
||||||
cryptoLen = currentCryptoLen
|
|
||||||
}
|
}
|
||||||
if _, err := buffer.Read(cryptoDataBuf.BytesRange(int32(offset), currentCryptoLen)); err != nil { // Field: Crypto Data
|
if _, err := buffer.Read(cryptoData[offset : offset+length]); err != nil { // Field: Crypto Data
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet
|
case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet
|
||||||
@ -253,7 +267,7 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tlsHdr := &ptls.SniffHeader{}
|
tlsHdr := &ptls.SniffHeader{}
|
||||||
err = ptls.ReadClientHello(cryptoDataBuf.BytesRange(0, cryptoLen), tlsHdr)
|
err = ptls.ReadClientHello(cryptoData[:cryptoLen], tlsHdr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// The crypto data may have not been fully recovered in current packets,
|
// The crypto data may have not been fully recovered in current packets,
|
||||||
// So we continue to sniff rest packets.
|
// So we continue to sniff rest packets.
|
||||||
@ -262,8 +276,7 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
}
|
}
|
||||||
return &SniffHeader{domain: tlsHdr.Domain()}, nil
|
return &SniffHeader{domain: tlsHdr.Domain()}, nil
|
||||||
}
|
}
|
||||||
// All payload is parsed as valid QUIC packets, but we need more packets for crypto data to read client hello.
|
return nil, common.ErrNoClue
|
||||||
return nil, protocol.ErrProtoNeedMoreData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte {
|
func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte {
|
||||||
|
File diff suppressed because one or more lines are too long
2
common/protocol/tls/cert/.gitignore
vendored
Normal file
2
common/protocol/tls/cert/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*.crt
|
||||||
|
*.key
|
@ -3,9 +3,9 @@ package tls
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SniffHeader struct {
|
type SniffHeader struct {
|
||||||
@ -59,6 +59,9 @@ func ReadClientHello(data []byte, h *SniffHeader) error {
|
|||||||
}
|
}
|
||||||
data = data[1+compressionMethodsLen:]
|
data = data[1+compressionMethodsLen:]
|
||||||
|
|
||||||
|
if len(data) == 0 {
|
||||||
|
return errNotClientHello
|
||||||
|
}
|
||||||
if len(data) < 2 {
|
if len(data) < 2 {
|
||||||
return errNotClientHello
|
return errNotClientHello
|
||||||
}
|
}
|
||||||
@ -101,21 +104,13 @@ func ReadClientHello(data []byte, h *SniffHeader) error {
|
|||||||
return errNotClientHello
|
return errNotClientHello
|
||||||
}
|
}
|
||||||
if nameType == 0 {
|
if nameType == 0 {
|
||||||
// QUIC separated across packets
|
serverName := string(d[:nameLen])
|
||||||
// May cause the serverName to be incomplete
|
|
||||||
b := byte(0)
|
|
||||||
for _, b = range d[:nameLen] {
|
|
||||||
if b <= ' ' {
|
|
||||||
return protocol.ErrProtoNeedMoreData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// An SNI value may not include a
|
// An SNI value may not include a
|
||||||
// trailing dot. See
|
// trailing dot. See
|
||||||
// https://tools.ietf.org/html/rfc6066#section-3.
|
// https://tools.ietf.org/html/rfc6066#section-3.
|
||||||
if b == '.' {
|
if strings.HasSuffix(serverName, ".") {
|
||||||
return errNotClientHello
|
return errNotClientHello
|
||||||
}
|
}
|
||||||
serverName := string(d[:nameLen])
|
|
||||||
h.domain = serverName
|
h.domain = serverName
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ func ContextWithOutbounds(ctx context.Context, outbounds []*Outbound) context.Co
|
|||||||
return context.WithValue(ctx, outboundSessionKey, outbounds)
|
return context.WithValue(ctx, outboundSessionKey, outbounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ContextCloneOutboundsAndContent(ctx context.Context) context.Context {
|
func ContextCloneOutbounds(ctx context.Context) context.Context {
|
||||||
outbounds := OutboundsFromContext(ctx)
|
outbounds := OutboundsFromContext(ctx)
|
||||||
newOutbounds := make([]*Outbound, len(outbounds))
|
newOutbounds := make([]*Outbound, len(outbounds))
|
||||||
for i, ob := range outbounds {
|
for i, ob := range outbounds {
|
||||||
@ -55,15 +55,7 @@ func ContextCloneOutboundsAndContent(ctx context.Context) context.Context {
|
|||||||
newOutbounds[i] = &v
|
newOutbounds[i] = &v
|
||||||
}
|
}
|
||||||
|
|
||||||
content := ContentFromContext(ctx)
|
return ContextWithOutbounds(ctx, newOutbounds)
|
||||||
newContent := Content{}
|
|
||||||
if content != nil {
|
|
||||||
newContent = *content
|
|
||||||
if content.Attributes != nil {
|
|
||||||
panic("content.Attributes != nil")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ContextWithContent(ContextWithOutbounds(ctx, newOutbounds), &newContent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func OutboundsFromContext(ctx context.Context) []*Outbound {
|
func OutboundsFromContext(ctx context.Context) []*Outbound {
|
||||||
|
@ -4,6 +4,7 @@ package session // import "github.com/xtls/xray-core/common/session"
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"sync"
|
||||||
|
|
||||||
c "github.com/xtls/xray-core/common/ctx"
|
c "github.com/xtls/xray-core/common/ctx"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
@ -74,8 +75,8 @@ type Outbound struct {
|
|||||||
|
|
||||||
// SniffingRequest controls the behavior of content sniffing.
|
// SniffingRequest controls the behavior of content sniffing.
|
||||||
type SniffingRequest struct {
|
type SniffingRequest struct {
|
||||||
ExcludeForDomain []string // read-only once set
|
ExcludeForDomain []string
|
||||||
OverrideDestinationForProtocol []string // read-only once set
|
OverrideDestinationForProtocol []string
|
||||||
Enabled bool
|
Enabled bool
|
||||||
MetadataOnly bool
|
MetadataOnly bool
|
||||||
RouteOnly bool
|
RouteOnly bool
|
||||||
@ -91,6 +92,10 @@ type Content struct {
|
|||||||
Attributes map[string]string
|
Attributes map[string]string
|
||||||
|
|
||||||
SkipDNSResolve bool
|
SkipDNSResolve bool
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
|
||||||
|
isLocked bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sockopt is the settings for socket connection.
|
// Sockopt is the settings for socket connection.
|
||||||
@ -99,8 +104,22 @@ type Sockopt struct {
|
|||||||
Mark int32
|
Mark int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some how when using mux, there will be a same ctx between different requests
|
||||||
|
// This will cause problem as it's designed for single request, like concurrent map writes
|
||||||
|
// Add a Mutex as a temp solution
|
||||||
|
|
||||||
// SetAttribute attaches additional string attributes to content.
|
// SetAttribute attaches additional string attributes to content.
|
||||||
func (c *Content) SetAttribute(name string, value string) {
|
func (c *Content) SetAttribute(name string, value string) {
|
||||||
|
if c.isLocked {
|
||||||
|
errors.LogError(context.Background(), "Multiple goroutines are tring to access one routing content, tring to write ", name, ":", value)
|
||||||
|
}
|
||||||
|
c.mu.Lock()
|
||||||
|
c.isLocked = true
|
||||||
|
defer func() {
|
||||||
|
c.isLocked = false
|
||||||
|
c.mu.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
if c.Attributes == nil {
|
if c.Attributes == nil {
|
||||||
c.Attributes = make(map[string]string)
|
c.Attributes = make(map[string]string)
|
||||||
}
|
}
|
||||||
@ -109,8 +128,24 @@ func (c *Content) SetAttribute(name string, value string) {
|
|||||||
|
|
||||||
// Attribute retrieves additional string attributes from content.
|
// Attribute retrieves additional string attributes from content.
|
||||||
func (c *Content) Attribute(name string) string {
|
func (c *Content) Attribute(name string) string {
|
||||||
|
c.mu.Lock()
|
||||||
|
c.isLocked = true
|
||||||
|
defer func() {
|
||||||
|
c.isLocked = false
|
||||||
|
c.mu.Unlock()
|
||||||
|
}()
|
||||||
if c.Attributes == nil {
|
if c.Attributes == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return c.Attributes[name]
|
return c.Attributes[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Content) AttributeLen() int {
|
||||||
|
c.mu.Lock()
|
||||||
|
c.isLocked = true
|
||||||
|
defer func() {
|
||||||
|
c.isLocked = false
|
||||||
|
c.mu.Unlock()
|
||||||
|
}()
|
||||||
|
return len(c.Attributes)
|
||||||
|
}
|
||||||
|
@ -18,8 +18,8 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
Version_x byte = 25
|
Version_x byte = 25
|
||||||
Version_y byte = 5
|
Version_y byte = 2
|
||||||
Version_z byte = 16
|
Version_z byte = 21
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -21,7 +21,11 @@ type Client interface {
|
|||||||
features.Feature
|
features.Feature
|
||||||
|
|
||||||
// LookupIP returns IP address for the given domain. IPs may contain IPv4 and/or IPv6 addresses.
|
// LookupIP returns IP address for the given domain. IPs may contain IPv4 and/or IPv6 addresses.
|
||||||
LookupIP(domain string, option IPOption) ([]net.IP, uint32, error)
|
LookupIP(domain string, option IPOption) ([]net.IP, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HostsLookup interface {
|
||||||
|
LookupHosts(domain string) *net.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientType returns the type of Client interface. Can be used for implementing common.HasType.
|
// ClientType returns the type of Client interface. Can be used for implementing common.HasType.
|
||||||
@ -34,8 +38,6 @@ func ClientType() interface{} {
|
|||||||
// ErrEmptyResponse indicates that DNS query succeeded but no answer was returned.
|
// ErrEmptyResponse indicates that DNS query succeeded but no answer was returned.
|
||||||
var ErrEmptyResponse = errors.New("empty response")
|
var ErrEmptyResponse = errors.New("empty response")
|
||||||
|
|
||||||
const DefaultTTL = 300
|
|
||||||
|
|
||||||
type RCodeError uint16
|
type RCodeError uint16
|
||||||
|
|
||||||
func (e RCodeError) Error() string {
|
func (e RCodeError) Error() string {
|
||||||
|
@ -20,44 +20,41 @@ func (*Client) Start() error { return nil }
|
|||||||
func (*Client) Close() error { return nil }
|
func (*Client) Close() error { return nil }
|
||||||
|
|
||||||
// LookupIP implements Client.
|
// LookupIP implements Client.
|
||||||
func (*Client) LookupIP(host string, option dns.IPOption) ([]net.IP, uint32, error) {
|
func (*Client) LookupIP(host string, option dns.IPOption) ([]net.IP, error) {
|
||||||
ips, err := net.LookupIP(host)
|
ips, err := net.LookupIP(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, err
|
||||||
}
|
}
|
||||||
parsedIPs := make([]net.IP, 0, len(ips))
|
parsedIPs := make([]net.IP, 0, len(ips))
|
||||||
ipv4 := make([]net.IP, 0, len(ips))
|
ipv4 := make([]net.IP, 0, len(ips))
|
||||||
ipv6 := make([]net.IP, 0, len(ips))
|
ipv6 := make([]net.IP, 0, len(ips))
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
parsed := net.IPAddress(ip)
|
parsed := net.IPAddress(ip)
|
||||||
if parsed == nil {
|
if parsed != nil {
|
||||||
continue
|
parsedIPs = append(parsedIPs, parsed.IP())
|
||||||
}
|
}
|
||||||
parsedIP := parsed.IP()
|
if len(ip) == net.IPv4len {
|
||||||
parsedIPs = append(parsedIPs, parsedIP)
|
ipv4 = append(ipv4, ip)
|
||||||
|
}
|
||||||
if len(parsedIP) == net.IPv4len {
|
if len(ip) == net.IPv6len {
|
||||||
ipv4 = append(ipv4, parsedIP)
|
ipv6 = append(ipv6, ip)
|
||||||
} else {
|
|
||||||
ipv6 = append(ipv6, parsedIP)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case option.IPv4Enable && option.IPv6Enable:
|
case option.IPv4Enable && option.IPv6Enable:
|
||||||
if len(parsedIPs) > 0 {
|
if len(parsedIPs) > 0 {
|
||||||
return parsedIPs, dns.DefaultTTL, nil
|
return parsedIPs, nil
|
||||||
}
|
}
|
||||||
case option.IPv4Enable:
|
case option.IPv4Enable:
|
||||||
if len(ipv4) > 0 {
|
if len(ipv4) > 0 {
|
||||||
return ipv4, dns.DefaultTTL, nil
|
return ipv4, nil
|
||||||
}
|
}
|
||||||
case option.IPv6Enable:
|
case option.IPv6Enable:
|
||||||
if len(ipv6) > 0 {
|
if len(ipv6) > 0 {
|
||||||
return ipv6, dns.DefaultTTL, nil
|
return ipv6, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, 0, dns.ErrEmptyResponse
|
return nil, dns.ErrEmptyResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
// New create a new dns.Client that queries localhost for DNS.
|
// New create a new dns.Client that queries localhost for DNS.
|
||||||
|
@ -23,7 +23,7 @@ func (ctx *ResolvableContext) GetTargetIPs() []net.IP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if domain := ctx.GetTargetDomain(); len(domain) != 0 {
|
if domain := ctx.GetTargetDomain(); len(domain) != 0 {
|
||||||
ips, _, err := ctx.dnsClient.LookupIP(domain, dns.IPOption{
|
ips, err := ctx.dnsClient.LookupIP(domain, dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
|
45
go.mod
45
go.mod
@ -1,37 +1,37 @@
|
|||||||
module github.com/xtls/xray-core
|
module github.com/xtls/xray-core
|
||||||
|
|
||||||
go 1.24
|
go 1.23
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0
|
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0
|
||||||
github.com/cloudflare/circl v1.6.1
|
github.com/cloudflare/circl v1.6.0
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
|
||||||
github.com/golang/mock v1.7.0-rc.1
|
github.com/golang/mock v1.7.0-rc.1
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.6.0
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/miekg/dns v1.1.66
|
github.com/miekg/dns v1.1.63
|
||||||
github.com/pelletier/go-toml v1.9.5
|
github.com/pelletier/go-toml v1.9.5
|
||||||
github.com/pires/go-proxyproto v0.8.1
|
github.com/pires/go-proxyproto v0.8.0
|
||||||
github.com/quic-go/quic-go v0.51.0
|
github.com/quic-go/quic-go v0.50.0
|
||||||
github.com/refraction-networking/utls v1.7.3
|
github.com/refraction-networking/utls v1.6.7
|
||||||
github.com/sagernet/sing v0.5.1
|
github.com/sagernet/sing v0.5.1
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771
|
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e
|
||||||
github.com/vishvananda/netlink v1.3.1
|
github.com/vishvananda/netlink v1.3.0
|
||||||
github.com/xtls/reality v0.0.0-20250516070713-4df2ec9a5b47
|
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/crypto v0.38.0
|
golang.org/x/crypto v0.33.0
|
||||||
golang.org/x/net v0.40.0
|
golang.org/x/net v0.35.0
|
||||||
golang.org/x/sync v0.14.0
|
golang.org/x/sync v0.11.0
|
||||||
golang.org/x/sys v0.33.0
|
golang.org/x/sys v0.30.0
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||||
google.golang.org/grpc v1.72.1
|
google.golang.org/grpc v1.70.0
|
||||||
google.golang.org/protobuf v1.36.6
|
google.golang.org/protobuf v1.36.5
|
||||||
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5
|
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0
|
||||||
h12.io/socks v1.0.3
|
h12.io/socks v1.0.3
|
||||||
lukechampine.com/blake3 v1.4.1
|
lukechampine.com/blake3 v1.3.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@ -47,14 +47,15 @@ require (
|
|||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
github.com/vishvananda/netns v0.0.5 // indirect
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
go.uber.org/mock v0.5.0 // indirect
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
golang.org/x/mod v0.24.0 // indirect
|
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect
|
||||||
golang.org/x/text v0.25.0 // indirect
|
golang.org/x/mod v0.21.0 // indirect
|
||||||
|
golang.org/x/text v0.22.0 // indirect
|
||||||
golang.org/x/time v0.7.0 // indirect
|
golang.org/x/time v0.7.0 // indirect
|
||||||
golang.org/x/tools v0.32.0 // indirect
|
golang.org/x/tools v0.26.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
108
go.sum
108
go.sum
@ -2,8 +2,8 @@ github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJS
|
|||||||
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM=
|
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM=
|
||||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
|
||||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@ -24,8 +24,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
|
|||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g=
|
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g=
|
||||||
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
@ -38,8 +38,8 @@ github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0N
|
|||||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE=
|
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||||
github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE=
|
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
||||||
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
|
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
|
||||||
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
||||||
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
|
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
|
||||||
@ -48,16 +48,16 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
|
|||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
||||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
||||||
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0=
|
||||||
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||||
github.com/quic-go/quic-go v0.51.0 h1:K8exxe9zXxeRKxaXxi/GpUqYiTrtdiWP8bo1KFya6Wc=
|
github.com/quic-go/quic-go v0.50.0 h1:3H/ld1pa3CYhkcc20TPIyG1bNsdhn9qZBGN3b9/UyUo=
|
||||||
github.com/quic-go/quic-go v0.51.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ=
|
github.com/quic-go/quic-go v0.50.0/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
|
||||||
github.com/refraction-networking/utls v1.7.3 h1:L0WRhHY7Oq1T0zkdzVZMR6zWZv+sXbHB9zcuvsAEqCo=
|
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
|
||||||
github.com/refraction-networking/utls v1.7.3/go.mod h1:TUhh27RHMGtQvjQq+RyO11P6ZNQNBb3N0v7wsEjKAIQ=
|
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||||
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
|
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
|
||||||
@ -72,45 +72,45 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
|
|||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||||
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
|
||||||
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
|
||||||
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
github.com/xtls/reality v0.0.0-20250516070713-4df2ec9a5b47 h1:9aJWkgWBwZ83l3j7+hBh3SurvRKuNfCgsSner5n6BcM=
|
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d h1:+B97uD9uHLgAAulhigmys4BVwZZypzK7gPN3WtpgRJg=
|
||||||
github.com/xtls/reality v0.0.0-20250516070713-4df2ec9a5b47/go.mod h1:bJdU3ExzfUlY40Xxfibq3THW9IHiE8mHu/tEzud5JWM=
|
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
|
||||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
|
||||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
|
||||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
|
||||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
|
||||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
|
||||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
|
||||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
|
||||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
|
||||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
|
||||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||||
|
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
|
||||||
|
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -119,21 +119,21 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||||
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||||
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@ -141,12 +141,12 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
|
|||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
|
||||||
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
|
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||||
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
@ -156,9 +156,9 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5 h1:sfK5nHuG7lRFZ2FdTT3RimOqWBg8IrVm+/Vko1FVOsk=
|
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 h1:P+U/06iIKPQ3DLcg+zBfSCia1luZ2msPZrJ8jYDFPs0=
|
||||||
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
|
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0=
|
||||||
h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
|
h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
|
||||||
h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
|
h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
|
||||||
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
||||||
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
|
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||||
|
@ -8,13 +8,11 @@ import (
|
|||||||
|
|
||||||
type Duration int64
|
type Duration int64
|
||||||
|
|
||||||
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
|
|
||||||
func (d *Duration) MarshalJSON() ([]byte, error) {
|
func (d *Duration) MarshalJSON() ([]byte, error) {
|
||||||
dr := time.Duration(*d)
|
dr := time.Duration(*d)
|
||||||
return json.Marshal(dr.String())
|
return json.Marshal(dr.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
|
|
||||||
func (d *Duration) UnmarshalJSON(b []byte) error {
|
func (d *Duration) UnmarshalJSON(b []byte) error {
|
||||||
var v interface{}
|
var v interface{}
|
||||||
if err := json.Unmarshal(b, &v); err != nil {
|
if err := json.Unmarshal(b, &v); err != nil {
|
||||||
|
@ -23,7 +23,6 @@ func (v StringList) Len() int {
|
|||||||
return len(v)
|
return len(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
|
|
||||||
func (v *StringList) UnmarshalJSON(data []byte) error {
|
func (v *StringList) UnmarshalJSON(data []byte) error {
|
||||||
var strarray []string
|
var strarray []string
|
||||||
if err := json.Unmarshal(data, &strarray); err == nil {
|
if err := json.Unmarshal(data, &strarray); err == nil {
|
||||||
@ -44,12 +43,10 @@ type Address struct {
|
|||||||
net.Address
|
net.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
|
func (v Address) MarshalJSON() ([]byte, error) {
|
||||||
func (v *Address) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(v.Address.String())
|
return json.Marshal(v.Address.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
|
|
||||||
func (v *Address) UnmarshalJSON(data []byte) error {
|
func (v *Address) UnmarshalJSON(data []byte) error {
|
||||||
var rawStr string
|
var rawStr string
|
||||||
if err := json.Unmarshal(data, &rawStr); err != nil {
|
if err := json.Unmarshal(data, &rawStr); err != nil {
|
||||||
@ -84,7 +81,6 @@ func (v Network) Build() net.Network {
|
|||||||
|
|
||||||
type NetworkList []Network
|
type NetworkList []Network
|
||||||
|
|
||||||
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
|
|
||||||
func (v *NetworkList) UnmarshalJSON(data []byte) error {
|
func (v *NetworkList) UnmarshalJSON(data []byte) error {
|
||||||
var strarray []Network
|
var strarray []Network
|
||||||
if err := json.Unmarshal(data, &strarray); err == nil {
|
if err := json.Unmarshal(data, &strarray); err == nil {
|
||||||
@ -173,19 +169,6 @@ func (v *PortRange) Build() *net.PortRange {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
|
|
||||||
func (v *PortRange) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(v.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (port *PortRange) String() string {
|
|
||||||
if port.From == port.To {
|
|
||||||
return strconv.Itoa(int(port.From))
|
|
||||||
} else {
|
|
||||||
return fmt.Sprintf("%d-%d", port.From, port.To)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
|
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
|
||||||
func (v *PortRange) UnmarshalJSON(data []byte) error {
|
func (v *PortRange) UnmarshalJSON(data []byte) error {
|
||||||
port, err := parseIntPort(data)
|
port, err := parseIntPort(data)
|
||||||
@ -220,21 +203,20 @@ func (list *PortList) Build() *net.PortList {
|
|||||||
return portList
|
return portList
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
|
func (v PortList) MarshalJSON() ([]byte, error) {
|
||||||
func (v *PortList) MarshalJSON() ([]byte, error) {
|
return json.Marshal(v.String())
|
||||||
portStr := v.String()
|
|
||||||
port, err := strconv.Atoi(portStr)
|
|
||||||
if err == nil {
|
|
||||||
return json.Marshal(port)
|
|
||||||
} else {
|
|
||||||
return json.Marshal(portStr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v PortList) String() string {
|
func (v PortList) String() string {
|
||||||
ports := []string{}
|
ports := []string{}
|
||||||
for _, port := range v.Range {
|
for _, port := range v.Range {
|
||||||
ports = append(ports, port.String())
|
if port.From == port.To {
|
||||||
|
p := strconv.Itoa(int(port.From))
|
||||||
|
ports = append(ports, p)
|
||||||
|
} else {
|
||||||
|
p := fmt.Sprintf("%d-%d", port.From, port.To)
|
||||||
|
ports = append(ports, p)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return strings.Join(ports, ",")
|
return strings.Join(ports, ",")
|
||||||
}
|
}
|
||||||
@ -295,8 +277,7 @@ type Int32Range struct {
|
|||||||
To int32
|
To int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
|
func (v Int32Range) MarshalJSON() ([]byte, error) {
|
||||||
func (v *Int32Range) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(v.String())
|
return json.Marshal(v.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,7 +289,6 @@ func (v Int32Range) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
|
|
||||||
func (v *Int32Range) UnmarshalJSON(data []byte) error {
|
func (v *Int32Range) UnmarshalJSON(data []byte) error {
|
||||||
defer v.ensureOrder()
|
defer v.ensureOrder()
|
||||||
var str string
|
var str string
|
||||||
|
@ -17,15 +17,10 @@ type NameServerConfig struct {
|
|||||||
Port uint16 `json:"port"`
|
Port uint16 `json:"port"`
|
||||||
SkipFallback bool `json:"skipFallback"`
|
SkipFallback bool `json:"skipFallback"`
|
||||||
Domains []string `json:"domains"`
|
Domains []string `json:"domains"`
|
||||||
ExpectedIPs StringList `json:"expectedIPs"`
|
ExpectIPs StringList `json:"expectIps"`
|
||||||
ExpectIPs StringList `json:"expectIPs"`
|
|
||||||
QueryStrategy string `json:"queryStrategy"`
|
QueryStrategy string `json:"queryStrategy"`
|
||||||
AllowUnexpectedIPs bool `json:"allowUnexpectedIps"`
|
|
||||||
Tag string `json:"tag"`
|
|
||||||
TimeoutMs uint64 `json:"timeoutMs"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
|
|
||||||
func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
|
func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
|
||||||
var address Address
|
var address Address
|
||||||
if err := json.Unmarshal(data, &address); err == nil {
|
if err := json.Unmarshal(data, &address); err == nil {
|
||||||
@ -39,12 +34,8 @@ func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
|
|||||||
Port uint16 `json:"port"`
|
Port uint16 `json:"port"`
|
||||||
SkipFallback bool `json:"skipFallback"`
|
SkipFallback bool `json:"skipFallback"`
|
||||||
Domains []string `json:"domains"`
|
Domains []string `json:"domains"`
|
||||||
ExpectedIPs StringList `json:"expectedIPs"`
|
ExpectIPs StringList `json:"expectIps"`
|
||||||
ExpectIPs StringList `json:"expectIPs"`
|
|
||||||
QueryStrategy string `json:"queryStrategy"`
|
QueryStrategy string `json:"queryStrategy"`
|
||||||
AllowUnexpectedIPs bool `json:"allowUnexpectedIps"`
|
|
||||||
Tag string `json:"tag"`
|
|
||||||
TimeoutMs uint64 `json:"timeoutMs"`
|
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(data, &advanced); err == nil {
|
if err := json.Unmarshal(data, &advanced); err == nil {
|
||||||
c.Address = advanced.Address
|
c.Address = advanced.Address
|
||||||
@ -52,12 +43,8 @@ func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
|
|||||||
c.Port = advanced.Port
|
c.Port = advanced.Port
|
||||||
c.SkipFallback = advanced.SkipFallback
|
c.SkipFallback = advanced.SkipFallback
|
||||||
c.Domains = advanced.Domains
|
c.Domains = advanced.Domains
|
||||||
c.ExpectedIPs = advanced.ExpectedIPs
|
|
||||||
c.ExpectIPs = advanced.ExpectIPs
|
c.ExpectIPs = advanced.ExpectIPs
|
||||||
c.QueryStrategy = advanced.QueryStrategy
|
c.QueryStrategy = advanced.QueryStrategy
|
||||||
c.AllowUnexpectedIPs = advanced.AllowUnexpectedIPs
|
|
||||||
c.Tag = advanced.Tag
|
|
||||||
c.TimeoutMs = advanced.TimeoutMs
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,13 +92,9 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var expectedIPs = c.ExpectedIPs
|
geoipList, err := ToCidrList(c.ExpectIPs)
|
||||||
if len(expectedIPs) == 0 {
|
|
||||||
expectedIPs = c.ExpectIPs
|
|
||||||
}
|
|
||||||
geoipList, err := ToCidrList(expectedIPs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("invalid IP rule: ", expectedIPs).Base(err)
|
return nil, errors.New("invalid IP rule: ", c.ExpectIPs).Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var myClientIP []byte
|
var myClientIP []byte
|
||||||
@ -134,9 +117,6 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
|
|||||||
Geoip: geoipList,
|
Geoip: geoipList,
|
||||||
OriginalRules: originalRules,
|
OriginalRules: originalRules,
|
||||||
QueryStrategy: resolveQueryStrategy(c.QueryStrategy),
|
QueryStrategy: resolveQueryStrategy(c.QueryStrategy),
|
||||||
AllowUnexpectedIPs: c.AllowUnexpectedIPs,
|
|
||||||
Tag: c.Tag,
|
|
||||||
TimeoutMs: c.TimeoutMs,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,18 +144,6 @@ type HostAddress struct {
|
|||||||
addrs []*Address
|
addrs []*Address
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
|
|
||||||
func (h *HostAddress) MarshalJSON() ([]byte, error) {
|
|
||||||
if (h.addr != nil) != (h.addrs != nil) {
|
|
||||||
if h.addr != nil {
|
|
||||||
return json.Marshal(h.addr)
|
|
||||||
} else if h.addrs != nil {
|
|
||||||
return json.Marshal(h.addrs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.New("unexpected config state")
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
|
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
|
||||||
func (h *HostAddress) UnmarshalJSON(data []byte) error {
|
func (h *HostAddress) UnmarshalJSON(data []byte) error {
|
||||||
addr := new(Address)
|
addr := new(Address)
|
||||||
@ -221,11 +189,6 @@ func getHostMapping(ha *HostAddress) *dns.Config_HostMapping {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
|
|
||||||
func (m *HostsWrapper) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(m.Hosts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
|
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
|
||||||
func (m *HostsWrapper) UnmarshalJSON(data []byte) error {
|
func (m *HostsWrapper) UnmarshalJSON(data []byte) error {
|
||||||
hosts := make(map[string]*HostAddress)
|
hosts := make(map[string]*HostAddress)
|
||||||
|
@ -20,18 +20,6 @@ type FakeDNSConfig struct {
|
|||||||
pools []*FakeDNSPoolElementConfig
|
pools []*FakeDNSPoolElementConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
|
|
||||||
func (f *FakeDNSConfig) MarshalJSON() ([]byte, error) {
|
|
||||||
if (f.pool != nil) != (f.pools != nil) {
|
|
||||||
if f.pool != nil {
|
|
||||||
return json.Marshal(f.pool)
|
|
||||||
} else if f.pools != nil {
|
|
||||||
return json.Marshal(f.pools)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.New("unexpected config state")
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
|
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
|
||||||
func (f *FakeDNSConfig) UnmarshalJSON(data []byte) error {
|
func (f *FakeDNSConfig) UnmarshalJSON(data []byte) error {
|
||||||
var pool FakeDNSPoolElementConfig
|
var pool FakeDNSPoolElementConfig
|
||||||
|
@ -334,7 +334,7 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
|||||||
|
|
||||||
func readFileOrString(f string, s []string) ([]byte, error) {
|
func readFileOrString(f string, s []string) ([]byte, error) {
|
||||||
if len(f) > 0 {
|
if len(f) > 0 {
|
||||||
return filesystem.ReadCert(f)
|
return filesystem.ReadFile(f)
|
||||||
}
|
}
|
||||||
if len(s) > 0 {
|
if len(s) > 0 {
|
||||||
return []byte(strings.Join(s, "\n")), nil
|
return []byte(strings.Join(s, "\n")), nil
|
||||||
@ -502,7 +502,6 @@ type REALITYConfig struct {
|
|||||||
|
|
||||||
Fingerprint string `json:"fingerprint"`
|
Fingerprint string `json:"fingerprint"`
|
||||||
ServerName string `json:"serverName"`
|
ServerName string `json:"serverName"`
|
||||||
Password string `json:"password"`
|
|
||||||
PublicKey string `json:"publicKey"`
|
PublicKey string `json:"publicKey"`
|
||||||
ShortId string `json:"shortId"`
|
ShortId string `json:"shortId"`
|
||||||
SpiderX string `json:"spiderX"`
|
SpiderX string `json:"spiderX"`
|
||||||
@ -611,14 +610,11 @@ func (c *REALITYConfig) Build() (proto.Message, error) {
|
|||||||
if len(c.ServerNames) != 0 {
|
if len(c.ServerNames) != 0 {
|
||||||
return nil, errors.New(`non-empty "serverNames", please use "serverName" instead`)
|
return nil, errors.New(`non-empty "serverNames", please use "serverName" instead`)
|
||||||
}
|
}
|
||||||
if c.Password != "" {
|
|
||||||
c.PublicKey = c.Password
|
|
||||||
}
|
|
||||||
if c.PublicKey == "" {
|
if c.PublicKey == "" {
|
||||||
return nil, errors.New(`empty "password"`)
|
return nil, errors.New(`empty "publicKey"`)
|
||||||
}
|
}
|
||||||
if config.PublicKey, err = base64.RawURLEncoding.DecodeString(c.PublicKey); err != nil || len(config.PublicKey) != 32 {
|
if config.PublicKey, err = base64.RawURLEncoding.DecodeString(c.PublicKey); err != nil || len(config.PublicKey) != 32 {
|
||||||
return nil, errors.New(`invalid "password": `, c.PublicKey)
|
return nil, errors.New(`invalid "publicKey": `, c.PublicKey)
|
||||||
}
|
}
|
||||||
if len(c.ShortIds) != 0 {
|
if len(c.ShortIds) != 0 {
|
||||||
return nil, errors.New(`non-empty "shortIds", please use "shortId" instead`)
|
return nil, errors.New(`non-empty "shortIds", please use "shortId" instead`)
|
||||||
@ -691,8 +687,6 @@ func (p TransportProtocol) Build() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CustomSockoptConfig struct {
|
type CustomSockoptConfig struct {
|
||||||
Syetem string `json:"system"`
|
|
||||||
Network string `json:"network"`
|
|
||||||
Level string `json:"level"`
|
Level string `json:"level"`
|
||||||
Opt string `json:"opt"`
|
Opt string `json:"opt"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
@ -717,7 +711,6 @@ type SocketConfig struct {
|
|||||||
Interface string `json:"interface"`
|
Interface string `json:"interface"`
|
||||||
TcpMptcp bool `json:"tcpMptcp"`
|
TcpMptcp bool `json:"tcpMptcp"`
|
||||||
CustomSockopt []*CustomSockoptConfig `json:"customSockopt"`
|
CustomSockopt []*CustomSockoptConfig `json:"customSockopt"`
|
||||||
AddressPortStrategy string `json:"addressPortStrategy"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build implements Buildable.
|
// Build implements Buildable.
|
||||||
@ -779,8 +772,6 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
|
|||||||
|
|
||||||
for _, copt := range c.CustomSockopt {
|
for _, copt := range c.CustomSockopt {
|
||||||
customSockopt := &internet.CustomSockopt{
|
customSockopt := &internet.CustomSockopt{
|
||||||
System: copt.Syetem,
|
|
||||||
Network: copt.Network,
|
|
||||||
Level: copt.Level,
|
Level: copt.Level,
|
||||||
Opt: copt.Opt,
|
Opt: copt.Opt,
|
||||||
Value: copt.Value,
|
Value: copt.Value,
|
||||||
@ -789,26 +780,6 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
|
|||||||
customSockopts = append(customSockopts, customSockopt)
|
customSockopts = append(customSockopts, customSockopt)
|
||||||
}
|
}
|
||||||
|
|
||||||
addressPortStrategy := internet.AddressPortStrategy_None
|
|
||||||
switch strings.ToLower(c.AddressPortStrategy) {
|
|
||||||
case "none", "":
|
|
||||||
addressPortStrategy = internet.AddressPortStrategy_None
|
|
||||||
case "srvportonly":
|
|
||||||
addressPortStrategy = internet.AddressPortStrategy_SrvPortOnly
|
|
||||||
case "srvaddressonly":
|
|
||||||
addressPortStrategy = internet.AddressPortStrategy_SrvAddressOnly
|
|
||||||
case "srvportandaddress":
|
|
||||||
addressPortStrategy = internet.AddressPortStrategy_SrvPortAndAddress
|
|
||||||
case "txtportonly":
|
|
||||||
addressPortStrategy = internet.AddressPortStrategy_TxtPortOnly
|
|
||||||
case "txtaddressonly":
|
|
||||||
addressPortStrategy = internet.AddressPortStrategy_TxtAddressOnly
|
|
||||||
case "txtportandaddress":
|
|
||||||
addressPortStrategy = internet.AddressPortStrategy_TxtPortAndAddress
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unsupported address and port strategy: ", c.AddressPortStrategy)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &internet.SocketConfig{
|
return &internet.SocketConfig{
|
||||||
Mark: c.Mark,
|
Mark: c.Mark,
|
||||||
Tfo: tfo,
|
Tfo: tfo,
|
||||||
@ -827,7 +798,6 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
|
|||||||
Interface: c.Interface,
|
Interface: c.Interface,
|
||||||
TcpMptcp: c.TcpMptcp,
|
TcpMptcp: c.TcpMptcp,
|
||||||
CustomSockopt: customSockopts,
|
CustomSockopt: customSockopts,
|
||||||
AddressPortStrategy: addressPortStrategy,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ func (c *WireGuardConfig) Build() (proto.Message, error) {
|
|||||||
var err error
|
var err error
|
||||||
config.SecretKey, err = ParseWireGuardKey(c.SecretKey)
|
config.SecretKey, err = ParseWireGuardKey(c.SecretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("invalid WireGuard secret key: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Address == nil {
|
if c.Address == nil {
|
||||||
@ -126,10 +126,6 @@ func (c *WireGuardConfig) Build() (proto.Message, error) {
|
|||||||
func ParseWireGuardKey(str string) (string, error) {
|
func ParseWireGuardKey(str string) (string, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if str == "" {
|
|
||||||
return "", errors.New("key must not be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(str)%2 == 0 {
|
if len(str)%2 == 0 {
|
||||||
_, err = hex.DecodeString(str)
|
_, err = hex.DecodeString(str)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -241,14 +241,14 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
|
|||||||
}
|
}
|
||||||
rawConfig, err := inboundConfigLoader.LoadWithID(settings, c.Protocol)
|
rawConfig, err := inboundConfigLoader.LoadWithID(settings, c.Protocol)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to load inbound detour config for protocol ", c.Protocol).Base(err)
|
return nil, errors.New("failed to load inbound detour config.").Base(err)
|
||||||
}
|
}
|
||||||
if dokodemoConfig, ok := rawConfig.(*DokodemoConfig); ok {
|
if dokodemoConfig, ok := rawConfig.(*DokodemoConfig); ok {
|
||||||
receiverSettings.ReceiveOriginalDestination = dokodemoConfig.Redirect
|
receiverSettings.ReceiveOriginalDestination = dokodemoConfig.Redirect
|
||||||
}
|
}
|
||||||
ts, err := rawConfig.(Buildable).Build()
|
ts, err := rawConfig.(Buildable).Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build inbound handler for protocol ", c.Protocol).Base(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &core.InboundHandlerConfig{
|
return &core.InboundHandlerConfig{
|
||||||
@ -303,7 +303,7 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
|
|||||||
if c.StreamSetting != nil {
|
if c.StreamSetting != nil {
|
||||||
ss, err := c.StreamSetting.Build()
|
ss, err := c.StreamSetting.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build stream settings for outbound detour").Base(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
senderSettings.StreamSettings = ss
|
senderSettings.StreamSettings = ss
|
||||||
}
|
}
|
||||||
@ -311,7 +311,7 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
|
|||||||
if c.ProxySettings != nil {
|
if c.ProxySettings != nil {
|
||||||
ps, err := c.ProxySettings.Build()
|
ps, err := c.ProxySettings.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("invalid outbound detour proxy settings").Base(err)
|
return nil, errors.New("invalid outbound detour proxy settings.").Base(err)
|
||||||
}
|
}
|
||||||
if ps.TransportLayerProxy {
|
if ps.TransportLayerProxy {
|
||||||
if senderSettings.StreamSettings != nil {
|
if senderSettings.StreamSettings != nil {
|
||||||
@ -331,7 +331,7 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
|
|||||||
if c.MuxSettings != nil {
|
if c.MuxSettings != nil {
|
||||||
ms, err := c.MuxSettings.Build()
|
ms, err := c.MuxSettings.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build Mux config").Base(err)
|
return nil, errors.New("failed to build Mux config.").Base(err)
|
||||||
}
|
}
|
||||||
senderSettings.MultiplexSettings = ms
|
senderSettings.MultiplexSettings = ms
|
||||||
}
|
}
|
||||||
@ -342,11 +342,11 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
|
|||||||
}
|
}
|
||||||
rawConfig, err := outboundConfigLoader.LoadWithID(settings, c.Protocol)
|
rawConfig, err := outboundConfigLoader.LoadWithID(settings, c.Protocol)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to load outbound detour config for protocol ", c.Protocol).Base(err)
|
return nil, errors.New("failed to parse to outbound detour config.").Base(err)
|
||||||
}
|
}
|
||||||
ts, err := rawConfig.(Buildable).Build()
|
ts, err := rawConfig.(Buildable).Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build outbound handler for protocol ", c.Protocol).Base(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &core.OutboundHandlerConfig{
|
return &core.OutboundHandlerConfig{
|
||||||
@ -490,7 +490,7 @@ func (c *Config) Override(o *Config, fn string) {
|
|||||||
// Build implements Buildable.
|
// Build implements Buildable.
|
||||||
func (c *Config) Build() (*core.Config, error) {
|
func (c *Config) Build() (*core.Config, error) {
|
||||||
if err := PostProcessConfigureFile(c); err != nil {
|
if err := PostProcessConfigureFile(c); err != nil {
|
||||||
return nil, errors.New("failed to post-process configuration file").Base(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
config := &core.Config{
|
config := &core.Config{
|
||||||
@ -504,21 +504,21 @@ func (c *Config) Build() (*core.Config, error) {
|
|||||||
if c.API != nil {
|
if c.API != nil {
|
||||||
apiConf, err := c.API.Build()
|
apiConf, err := c.API.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build API configuration").Base(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
config.App = append(config.App, serial.ToTypedMessage(apiConf))
|
config.App = append(config.App, serial.ToTypedMessage(apiConf))
|
||||||
}
|
}
|
||||||
if c.Metrics != nil {
|
if c.Metrics != nil {
|
||||||
metricsConf, err := c.Metrics.Build()
|
metricsConf, err := c.Metrics.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build metrics configuration").Base(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
config.App = append(config.App, serial.ToTypedMessage(metricsConf))
|
config.App = append(config.App, serial.ToTypedMessage(metricsConf))
|
||||||
}
|
}
|
||||||
if c.Stats != nil {
|
if c.Stats != nil {
|
||||||
statsConf, err := c.Stats.Build()
|
statsConf, err := c.Stats.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build stats configuration").Base(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
config.App = append(config.App, serial.ToTypedMessage(statsConf))
|
config.App = append(config.App, serial.ToTypedMessage(statsConf))
|
||||||
}
|
}
|
||||||
@ -536,7 +536,7 @@ func (c *Config) Build() (*core.Config, error) {
|
|||||||
if c.RouterConfig != nil {
|
if c.RouterConfig != nil {
|
||||||
routerConfig, err := c.RouterConfig.Build()
|
routerConfig, err := c.RouterConfig.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build routing configuration").Base(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
config.App = append(config.App, serial.ToTypedMessage(routerConfig))
|
config.App = append(config.App, serial.ToTypedMessage(routerConfig))
|
||||||
}
|
}
|
||||||
@ -544,7 +544,7 @@ func (c *Config) Build() (*core.Config, error) {
|
|||||||
if c.DNSConfig != nil {
|
if c.DNSConfig != nil {
|
||||||
dnsApp, err := c.DNSConfig.Build()
|
dnsApp, err := c.DNSConfig.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build DNS configuration").Base(err)
|
return nil, errors.New("failed to parse DNS config").Base(err)
|
||||||
}
|
}
|
||||||
config.App = append(config.App, serial.ToTypedMessage(dnsApp))
|
config.App = append(config.App, serial.ToTypedMessage(dnsApp))
|
||||||
}
|
}
|
||||||
@ -552,7 +552,7 @@ func (c *Config) Build() (*core.Config, error) {
|
|||||||
if c.Policy != nil {
|
if c.Policy != nil {
|
||||||
pc, err := c.Policy.Build()
|
pc, err := c.Policy.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build policy configuration").Base(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
config.App = append(config.App, serial.ToTypedMessage(pc))
|
config.App = append(config.App, serial.ToTypedMessage(pc))
|
||||||
}
|
}
|
||||||
@ -560,7 +560,7 @@ func (c *Config) Build() (*core.Config, error) {
|
|||||||
if c.Reverse != nil {
|
if c.Reverse != nil {
|
||||||
r, err := c.Reverse.Build()
|
r, err := c.Reverse.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build reverse configuration").Base(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
config.App = append(config.App, serial.ToTypedMessage(r))
|
config.App = append(config.App, serial.ToTypedMessage(r))
|
||||||
}
|
}
|
||||||
@ -568,7 +568,7 @@ func (c *Config) Build() (*core.Config, error) {
|
|||||||
if c.FakeDNS != nil {
|
if c.FakeDNS != nil {
|
||||||
r, err := c.FakeDNS.Build()
|
r, err := c.FakeDNS.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build fake DNS configuration").Base(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
config.App = append([]*serial.TypedMessage{serial.ToTypedMessage(r)}, config.App...)
|
config.App = append([]*serial.TypedMessage{serial.ToTypedMessage(r)}, config.App...)
|
||||||
}
|
}
|
||||||
@ -576,7 +576,7 @@ func (c *Config) Build() (*core.Config, error) {
|
|||||||
if c.Observatory != nil {
|
if c.Observatory != nil {
|
||||||
r, err := c.Observatory.Build()
|
r, err := c.Observatory.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build observatory configuration").Base(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
config.App = append(config.App, serial.ToTypedMessage(r))
|
config.App = append(config.App, serial.ToTypedMessage(r))
|
||||||
}
|
}
|
||||||
@ -584,7 +584,7 @@ func (c *Config) Build() (*core.Config, error) {
|
|||||||
if c.BurstObservatory != nil {
|
if c.BurstObservatory != nil {
|
||||||
r, err := c.BurstObservatory.Build()
|
r, err := c.BurstObservatory.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build burst observatory configuration").Base(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
config.App = append(config.App, serial.ToTypedMessage(r))
|
config.App = append(config.App, serial.ToTypedMessage(r))
|
||||||
}
|
}
|
||||||
@ -602,7 +602,7 @@ func (c *Config) Build() (*core.Config, error) {
|
|||||||
for _, rawInboundConfig := range inbounds {
|
for _, rawInboundConfig := range inbounds {
|
||||||
ic, err := rawInboundConfig.Build()
|
ic, err := rawInboundConfig.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build inbound config with tag ", rawInboundConfig.Tag).Base(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
config.Inbound = append(config.Inbound, ic)
|
config.Inbound = append(config.Inbound, ic)
|
||||||
}
|
}
|
||||||
@ -616,7 +616,7 @@ func (c *Config) Build() (*core.Config, error) {
|
|||||||
for _, rawOutboundConfig := range outbounds {
|
for _, rawOutboundConfig := range outbounds {
|
||||||
oc, err := rawOutboundConfig.Build()
|
oc, err := rawOutboundConfig.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to build outbound config with tag ", rawOutboundConfig.Tag).Base(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
config.Outbound = append(config.Outbound, oc)
|
config.Outbound = append(config.Outbound, oc)
|
||||||
}
|
}
|
||||||
|
@ -89,11 +89,12 @@ func whichProtoc(suffix, targetedVersion string) (string, error) {
|
|||||||
|
|
||||||
path, err := exec.LookPath(protoc)
|
path, err := exec.LookPath(protoc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf(`
|
errStr := fmt.Sprintf(`
|
||||||
Command "%s" not found.
|
Command "%s" not found.
|
||||||
Make sure that %s is in your system path or current path.
|
Make sure that %s is in your system path or current path.
|
||||||
Download %s v%s or later from https://github.com/protocolbuffers/protobuf/releases
|
Download %s v%s or later from https://github.com/protocolbuffers/protobuf/releases
|
||||||
`, protoc, protoc, protoc, targetedVersion)
|
`, protoc, protoc, protoc, targetedVersion)
|
||||||
|
return "", fmt.Errorf(errStr)
|
||||||
}
|
}
|
||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
|
@ -50,17 +50,17 @@ func executeTypedMessageToJson(cmd *base.Command, args []string) {
|
|||||||
|
|
||||||
reader, err := confloader.LoadConfig(cmd.Flag.Arg(0))
|
reader, err := confloader.LoadConfig(cmd.Flag.Arg(0))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("failed to load config: %s", err)
|
base.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := io.ReadAll(reader)
|
b, err := io.ReadAll(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("failed to read config: %s", err)
|
base.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
tm := cserial.TypedMessage{}
|
tm := cserial.TypedMessage{}
|
||||||
if err = json.Unmarshal(b, &tm); err != nil {
|
if err = json.Unmarshal(b, &tm); err != nil {
|
||||||
base.Fatalf("failed to unmarshal config: %s", err)
|
base.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if j, ok := creflect.MarshalToJson(&tm, injectTypeInfo); ok {
|
if j, ok := creflect.MarshalToJson(&tm, injectTypeInfo); ok {
|
||||||
|
@ -53,12 +53,12 @@ func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(unnamedArgs) < 1 {
|
if len(unnamedArgs) < 1 {
|
||||||
base.Fatalf("invalid config list length: %d", len(unnamedArgs))
|
base.Fatalf("empty config list")
|
||||||
}
|
}
|
||||||
|
|
||||||
pbConfig, err := core.LoadConfig("auto", unnamedArgs)
|
pbConfig, err := core.LoadConfig("auto", unnamedArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Fatalf("failed to load config: %s", err)
|
base.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if optDump {
|
if optDump {
|
||||||
|
@ -2,7 +2,6 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
go_errors "errors"
|
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -237,18 +236,17 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
|
|||||||
var ips []net.IP
|
var ips []net.IP
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
var ttl4 uint32
|
var ttl uint32 = 600
|
||||||
var ttl6 uint32
|
|
||||||
|
|
||||||
switch qType {
|
switch qType {
|
||||||
case dnsmessage.TypeA:
|
case dnsmessage.TypeA:
|
||||||
ips, ttl4, err = h.client.LookupIP(domain, dns.IPOption{
|
ips, err = h.client.LookupIP(domain, dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: false,
|
IPv6Enable: false,
|
||||||
FakeEnable: true,
|
FakeEnable: true,
|
||||||
})
|
})
|
||||||
case dnsmessage.TypeAAAA:
|
case dnsmessage.TypeAAAA:
|
||||||
ips, ttl6, err = h.client.LookupIP(domain, dns.IPOption{
|
ips, err = h.client.LookupIP(domain, dns.IPOption{
|
||||||
IPv4Enable: false,
|
IPv4Enable: false,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
FakeEnable: true,
|
FakeEnable: true,
|
||||||
@ -256,11 +254,15 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
rcode := dns.RCodeFromError(err)
|
rcode := dns.RCodeFromError(err)
|
||||||
if rcode == 0 && len(ips) == 0 && !go_errors.Is(err, dns.ErrEmptyResponse) {
|
if rcode == 0 && len(ips) == 0 && !errors.AllEqual(dns.ErrEmptyResponse, errors.Cause(err)) {
|
||||||
errors.LogInfoInner(context.Background(), err, "ip query")
|
errors.LogInfoInner(context.Background(), err, "ip query")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if fkr0, ok := h.fdns.(dns.FakeDNSEngineRev0); ok && len(ips) > 0 && fkr0.IsIPInIPPool(net.IPAddress(ips[0])) {
|
||||||
|
ttl = 1
|
||||||
|
}
|
||||||
|
|
||||||
switch qType {
|
switch qType {
|
||||||
case dnsmessage.TypeA:
|
case dnsmessage.TypeA:
|
||||||
for i, ip := range ips {
|
for i, ip := range ips {
|
||||||
@ -291,17 +293,16 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
|
|||||||
}))
|
}))
|
||||||
common.Must(builder.StartAnswers())
|
common.Must(builder.StartAnswers())
|
||||||
|
|
||||||
rHeader4 := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: ttl4}
|
rHeader := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: ttl}
|
||||||
rHeader6 := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: ttl6}
|
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
if len(ip) == net.IPv4len {
|
if len(ip) == net.IPv4len {
|
||||||
var r dnsmessage.AResource
|
var r dnsmessage.AResource
|
||||||
copy(r.A[:], ip)
|
copy(r.A[:], ip)
|
||||||
common.Must(builder.AResource(rHeader4, r))
|
common.Must(builder.AResource(rHeader, r))
|
||||||
} else {
|
} else {
|
||||||
var r dnsmessage.AAAAResource
|
var r dnsmessage.AAAAResource
|
||||||
copy(r.AAAA[:], ip)
|
copy(r.AAAA[:], ip)
|
||||||
common.Must(builder.AAAAResource(rHeader6, r))
|
common.Must(builder.AAAAResource(rHeader, r))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
msgBytes, err := builder.Finish()
|
msgBytes, err := builder.Finish()
|
||||||
|
@ -4,12 +4,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"io"
|
"io"
|
||||||
|
"math/big"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pires/go-proxyproto"
|
"github.com/pires/go-proxyproto"
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
"github.com/xtls/xray-core/common/crypto"
|
|
||||||
"github.com/xtls/xray-core/common/dice"
|
"github.com/xtls/xray-core/common/dice"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
@ -71,13 +71,13 @@ func (h *Handler) policy() policy.Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) resolveIP(ctx context.Context, domain string, localAddr net.Address) net.Address {
|
func (h *Handler) resolveIP(ctx context.Context, domain string, localAddr net.Address) net.Address {
|
||||||
ips, _, err := h.dns.LookupIP(domain, dns.IPOption{
|
ips, err := h.dns.LookupIP(domain, dns.IPOption{
|
||||||
IPv4Enable: (localAddr == nil || localAddr.Family().IsIPv4()) && h.config.preferIP4(),
|
IPv4Enable: (localAddr == nil || localAddr.Family().IsIPv4()) && h.config.preferIP4(),
|
||||||
IPv6Enable: (localAddr == nil || localAddr.Family().IsIPv6()) && h.config.preferIP6(),
|
IPv6Enable: (localAddr == nil || localAddr.Family().IsIPv6()) && h.config.preferIP6(),
|
||||||
})
|
})
|
||||||
{ // Resolve fallback
|
{ // Resolve fallback
|
||||||
if (len(ips) == 0 || err != nil) && h.config.hasFallback() && localAddr == nil {
|
if (len(ips) == 0 || err != nil) && h.config.hasFallback() && localAddr == nil {
|
||||||
ips, _, err = h.dns.LookupIP(domain, dns.IPOption{
|
ips, err = h.dns.LookupIP(domain, dns.IPOption{
|
||||||
IPv4Enable: h.config.fallbackIP4(),
|
IPv4Enable: h.config.fallbackIP4(),
|
||||||
IPv6Enable: h.config.fallbackIP6(),
|
IPv6Enable: h.config.fallbackIP6(),
|
||||||
})
|
})
|
||||||
@ -414,7 +414,7 @@ func (w *NoisePacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|||||||
noise = n.Packet
|
noise = n.Packet
|
||||||
} else {
|
} else {
|
||||||
//Random noise
|
//Random noise
|
||||||
noise, err = GenerateRandomBytes(crypto.RandBetween(int64(n.LengthMin),
|
noise, err = GenerateRandomBytes(randBetween(int64(n.LengthMin),
|
||||||
int64(n.LengthMax)))
|
int64(n.LengthMax)))
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -423,7 +423,7 @@ func (w *NoisePacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|||||||
w.Writer.WriteMultiBuffer(buf.MultiBuffer{buf.FromBytes(noise)})
|
w.Writer.WriteMultiBuffer(buf.MultiBuffer{buf.FromBytes(noise)})
|
||||||
|
|
||||||
if n.DelayMin != 0 || n.DelayMax != 0 {
|
if n.DelayMin != 0 || n.DelayMax != 0 {
|
||||||
time.Sleep(time.Duration(crypto.RandBetween(int64(n.DelayMin), int64(n.DelayMax))) * time.Millisecond)
|
time.Sleep(time.Duration(randBetween(int64(n.DelayMin), int64(n.DelayMax))) * time.Millisecond)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -452,7 +452,7 @@ func (f *FragmentWriter) Write(b []byte) (int, error) {
|
|||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
var hello []byte
|
var hello []byte
|
||||||
for from := 0; ; {
|
for from := 0; ; {
|
||||||
to := from + int(crypto.RandBetween(int64(f.fragment.LengthMin), int64(f.fragment.LengthMax)))
|
to := from + int(randBetween(int64(f.fragment.LengthMin), int64(f.fragment.LengthMax)))
|
||||||
if to > len(data) {
|
if to > len(data) {
|
||||||
to = len(data)
|
to = len(data)
|
||||||
}
|
}
|
||||||
@ -466,7 +466,7 @@ func (f *FragmentWriter) Write(b []byte) (int, error) {
|
|||||||
hello = append(hello, buf[:5+l]...)
|
hello = append(hello, buf[:5+l]...)
|
||||||
} else {
|
} else {
|
||||||
_, err := f.writer.Write(buf[:5+l])
|
_, err := f.writer.Write(buf[:5+l])
|
||||||
time.Sleep(time.Duration(crypto.RandBetween(int64(f.fragment.IntervalMin), int64(f.fragment.IntervalMax))) * time.Millisecond)
|
time.Sleep(time.Duration(randBetween(int64(f.fragment.IntervalMin), int64(f.fragment.IntervalMax))) * time.Millisecond)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -493,13 +493,13 @@ func (f *FragmentWriter) Write(b []byte) (int, error) {
|
|||||||
return f.writer.Write(b)
|
return f.writer.Write(b)
|
||||||
}
|
}
|
||||||
for from := 0; ; {
|
for from := 0; ; {
|
||||||
to := from + int(crypto.RandBetween(int64(f.fragment.LengthMin), int64(f.fragment.LengthMax)))
|
to := from + int(randBetween(int64(f.fragment.LengthMin), int64(f.fragment.LengthMax)))
|
||||||
if to > len(b) {
|
if to > len(b) {
|
||||||
to = len(b)
|
to = len(b)
|
||||||
}
|
}
|
||||||
n, err := f.writer.Write(b[from:to])
|
n, err := f.writer.Write(b[from:to])
|
||||||
from += n
|
from += n
|
||||||
time.Sleep(time.Duration(crypto.RandBetween(int64(f.fragment.IntervalMin), int64(f.fragment.IntervalMax))) * time.Millisecond)
|
time.Sleep(time.Duration(randBetween(int64(f.fragment.IntervalMin), int64(f.fragment.IntervalMax))) * time.Millisecond)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return from, err
|
return from, err
|
||||||
}
|
}
|
||||||
@ -509,6 +509,14 @@ func (f *FragmentWriter) Write(b []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stolen from github.com/xtls/xray-core/transport/internet/reality
|
||||||
|
func randBetween(left int64, right int64) int64 {
|
||||||
|
if left == right {
|
||||||
|
return left
|
||||||
|
}
|
||||||
|
bigInt, _ := rand.Int(rand.Reader, big.NewInt(right-left))
|
||||||
|
return left + bigInt.Int64()
|
||||||
|
}
|
||||||
func GenerateRandomBytes(n int64) ([]byte, error) {
|
func GenerateRandomBytes(n int64) ([]byte, error) {
|
||||||
b := make([]byte, n)
|
b := make([]byte, n)
|
||||||
_, err := rand.Read(b)
|
_, err := rand.Read(b)
|
||||||
|
@ -294,7 +294,7 @@ func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, wri
|
|||||||
|
|
||||||
responseDone := func() error {
|
responseDone := func() error {
|
||||||
responseReader := bufio.NewReaderSize(&buf.BufferedReader{Reader: link.Reader}, buf.Size)
|
responseReader := bufio.NewReaderSize(&buf.BufferedReader{Reader: link.Reader}, buf.Size)
|
||||||
response, err := readResponseAndHandle100Continue(responseReader, request, writer)
|
response, err := http.ReadResponse(responseReader, request)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
http_proto.RemoveHopByHopHeaders(response.Header)
|
http_proto.RemoveHopByHopHeaders(response.Header)
|
||||||
if response.ContentLength >= 0 {
|
if response.ContentLength >= 0 {
|
||||||
@ -338,38 +338,6 @@ func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, wri
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sometimes, server might send 1xx response to client
|
|
||||||
// it should not be processed by http proxy handler, just forward it to client
|
|
||||||
func readResponseAndHandle100Continue(r *bufio.Reader, req *http.Request, writer io.Writer) (*http.Response, error) {
|
|
||||||
// have a little look of response
|
|
||||||
peekBytes, err := r.Peek(56)
|
|
||||||
if err == nil || err == bufio.ErrBufferFull {
|
|
||||||
str := string(peekBytes)
|
|
||||||
ResponseLine := strings.Split(str, "\r\n")[0]
|
|
||||||
_, status, _ := strings.Cut(ResponseLine, " ")
|
|
||||||
// only handle 1xx response
|
|
||||||
if strings.HasPrefix(status, "1") {
|
|
||||||
ResponseHeader1xx := []byte{}
|
|
||||||
// read until \r\n\r\n (end of http response header)
|
|
||||||
for {
|
|
||||||
data, err := r.ReadSlice('\n')
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("failed to read http 1xx response").Base(err)
|
|
||||||
}
|
|
||||||
ResponseHeader1xx = append(ResponseHeader1xx, data...)
|
|
||||||
if bytes.Equal(ResponseHeader1xx[len(ResponseHeader1xx)-4:], []byte{'\r', '\n', '\r', '\n'}) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if len(ResponseHeader1xx) > 1024 {
|
|
||||||
return nil, errors.New("too big http 1xx response")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writer.Write(ResponseHeader1xx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return http.ReadResponse(r, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||||
return NewServer(ctx, config.(*ServerConfig))
|
return NewServer(ctx, config.(*ServerConfig))
|
||||||
|
@ -54,7 +54,7 @@ func (n *netBind) ParseEndpoint(s string) (conn.Endpoint, error) {
|
|||||||
|
|
||||||
addr := xnet.ParseAddress(ipStr)
|
addr := xnet.ParseAddress(ipStr)
|
||||||
if addr.Family() == xnet.AddressFamilyDomain {
|
if addr.Family() == xnet.AddressFamilyDomain {
|
||||||
ips, _, err := n.dns.LookupIP(addr.Domain(), n.dnsOption)
|
ips, err := n.dns.LookupIP(addr.Domain(), n.dnsOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if len(ips) == 0 {
|
} else if len(ips) == 0 {
|
||||||
|
@ -77,20 +77,6 @@ func New(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) Close() (err error) {
|
|
||||||
go func() {
|
|
||||||
h.wgLock.Lock()
|
|
||||||
defer h.wgLock.Unlock()
|
|
||||||
|
|
||||||
if h.net != nil {
|
|
||||||
_ = h.net.Close()
|
|
||||||
h.net = nil
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) processWireGuard(ctx context.Context, dialer internet.Dialer) (err error) {
|
func (h *Handler) processWireGuard(ctx context.Context, dialer internet.Dialer) (err error) {
|
||||||
h.wgLock.Lock()
|
h.wgLock.Lock()
|
||||||
defer h.wgLock.Unlock()
|
defer h.wgLock.Unlock()
|
||||||
@ -164,13 +150,13 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
// resolve dns
|
// resolve dns
|
||||||
addr := destination.Address
|
addr := destination.Address
|
||||||
if addr.Family().IsDomain() {
|
if addr.Family().IsDomain() {
|
||||||
ips, _, err := h.dns.LookupIP(addr.Domain(), dns.IPOption{
|
ips, err := h.dns.LookupIP(addr.Domain(), dns.IPOption{
|
||||||
IPv4Enable: h.hasIPv4 && h.conf.preferIP4(),
|
IPv4Enable: h.hasIPv4 && h.conf.preferIP4(),
|
||||||
IPv6Enable: h.hasIPv6 && h.conf.preferIP6(),
|
IPv6Enable: h.hasIPv6 && h.conf.preferIP6(),
|
||||||
})
|
})
|
||||||
{ // Resolve fallback
|
{ // Resolve fallback
|
||||||
if (len(ips) == 0 || err != nil) && h.conf.hasFallback() {
|
if (len(ips) == 0 || err != nil) && h.conf.hasFallback() {
|
||||||
ips, _, err = h.dns.LookupIP(addr.Domain(), dns.IPOption{
|
ips, err = h.dns.LookupIP(addr.Domain(), dns.IPOption{
|
||||||
IPv4Enable: h.hasIPv4 && h.conf.fallbackIP4(),
|
IPv4Enable: h.hasIPv4 && h.conf.fallbackIP4(),
|
||||||
IPv6Enable: h.hasIPv6 && h.conf.fallbackIP6(),
|
IPv6Enable: h.hasIPv6 && h.conf.fallbackIP6(),
|
||||||
})
|
})
|
||||||
@ -298,13 +284,13 @@ func (h *Handler) createIPCRequest() string {
|
|||||||
addr = net.ParseAddress(dialerIp.String())
|
addr = net.ParseAddress(dialerIp.String())
|
||||||
errors.LogInfo(h.bind.ctx, "createIPCRequest use dialer dest ip: ", addr)
|
errors.LogInfo(h.bind.ctx, "createIPCRequest use dialer dest ip: ", addr)
|
||||||
} else {
|
} else {
|
||||||
ips, _, err := h.dns.LookupIP(addr.Domain(), dns.IPOption{
|
ips, err := h.dns.LookupIP(addr.Domain(), dns.IPOption{
|
||||||
IPv4Enable: h.hasIPv4 && h.conf.preferIP4(),
|
IPv4Enable: h.hasIPv4 && h.conf.preferIP4(),
|
||||||
IPv6Enable: h.hasIPv6 && h.conf.preferIP6(),
|
IPv6Enable: h.hasIPv6 && h.conf.preferIP6(),
|
||||||
})
|
})
|
||||||
{ // Resolve fallback
|
{ // Resolve fallback
|
||||||
if (len(ips) == 0 || err != nil) && h.conf.hasFallback() {
|
if (len(ips) == 0 || err != nil) && h.conf.hasFallback() {
|
||||||
ips, _, err = h.dns.LookupIP(addr.Domain(), dns.IPOption{
|
ips, err = h.dns.LookupIP(addr.Domain(), dns.IPOption{
|
||||||
IPv4Enable: h.hasIPv4 && h.conf.fallbackIP4(),
|
IPv4Enable: h.hasIPv4 && h.conf.fallbackIP4(),
|
||||||
IPv6Enable: h.hasIPv6 && h.conf.fallbackIP6(),
|
IPv6Enable: h.hasIPv6 && h.conf.fallbackIP6(),
|
||||||
})
|
})
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
@ -34,7 +33,6 @@ type netTun struct {
|
|||||||
incomingPacket chan *buffer.View
|
incomingPacket chan *buffer.View
|
||||||
mtu int
|
mtu int
|
||||||
hasV4, hasV6 bool
|
hasV4, hasV6 bool
|
||||||
closeOnce sync.Once
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Net netTun
|
type Net netTun
|
||||||
@ -176,15 +174,18 @@ func (tun *netTun) Flush() error {
|
|||||||
|
|
||||||
// Close implements tun.Device
|
// Close implements tun.Device
|
||||||
func (tun *netTun) Close() error {
|
func (tun *netTun) Close() error {
|
||||||
tun.closeOnce.Do(func() {
|
|
||||||
tun.stack.RemoveNIC(1)
|
tun.stack.RemoveNIC(1)
|
||||||
|
|
||||||
|
if tun.events != nil {
|
||||||
close(tun.events)
|
close(tun.events)
|
||||||
|
}
|
||||||
|
|
||||||
tun.ep.Close()
|
tun.ep.Close()
|
||||||
|
|
||||||
|
if tun.incomingPacket != nil {
|
||||||
close(tun.incomingPacket)
|
close(tun.incomingPacket)
|
||||||
})
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
package wireguard_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"runtime/debug"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/core"
|
|
||||||
"github.com/xtls/xray-core/proxy/wireguard"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestWireGuardServerInitializationError verifies that an error during TUN initialization
|
|
||||||
// (triggered by an empty SecretKey) in the WireGuard server does not cause a panic and returns an error instead.
|
|
||||||
func TestWireGuardServerInitializationError(t *testing.T) {
|
|
||||||
// Create a minimal core instance with default features
|
|
||||||
config := &core.Config{}
|
|
||||||
instance, err := core.New(config)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create core instance: %v", err)
|
|
||||||
}
|
|
||||||
// Set the Xray instance in the context
|
|
||||||
ctx := context.WithValue(context.Background(), core.XrayKey(1), instance)
|
|
||||||
|
|
||||||
// Define the server configuration with an empty SecretKey to trigger error
|
|
||||||
conf := &wireguard.DeviceConfig{
|
|
||||||
IsClient: false,
|
|
||||||
Endpoint: []string{"10.0.0.1/32"},
|
|
||||||
Mtu: 1420,
|
|
||||||
SecretKey: "", // Empty SecretKey to trigger error
|
|
||||||
Peers: []*wireguard.PeerConfig{
|
|
||||||
{
|
|
||||||
PublicKey: "some_public_key",
|
|
||||||
AllowedIps: []string{"10.0.0.2/32"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use defer to catch any panic and fail the test explicitly
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
t.Errorf("TUN initialization panicked: %v", r)
|
|
||||||
debug.PrintStack()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Attempt to initialize the WireGuard server
|
|
||||||
_, err = wireguard.NewServer(ctx, conf)
|
|
||||||
|
|
||||||
// Check that an error is returned
|
|
||||||
assert.ErrorContains(t, err, "failed to set private_key: hex string does not fit the slice")
|
|
||||||
}
|
|
@ -50,13 +50,12 @@ func (mr *DNSClientMockRecorder) Close() *gomock.Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LookupIP mocks base method
|
// LookupIP mocks base method
|
||||||
func (m *DNSClient) LookupIP(arg0 string, arg1 dns.IPOption) ([]net.IP, uint32, error) {
|
func (m *DNSClient) LookupIP(arg0 string, arg1 dns.IPOption) ([]net.IP, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "LookupIP", arg0, arg1)
|
ret := m.ctrl.Call(m, "LookupIP", arg0, arg1)
|
||||||
ret0, _ := ret[0].([]net.IP)
|
ret0, _ := ret[0].([]net.IP)
|
||||||
ret1, _ := ret[1].(uint32)
|
ret1, _ := ret[1].(error)
|
||||||
ret2, _ := ret[2].(error)
|
return ret0, ret1
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupIP indicates an expected call of LookupIP
|
// LookupIP indicates an expected call of LookupIP
|
||||||
|
@ -95,67 +95,6 @@ func (DomainStrategy) EnumDescriptor() ([]byte, []int) {
|
|||||||
return file_transport_internet_config_proto_rawDescGZIP(), []int{0}
|
return file_transport_internet_config_proto_rawDescGZIP(), []int{0}
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddressPortStrategy int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
AddressPortStrategy_None AddressPortStrategy = 0
|
|
||||||
AddressPortStrategy_SrvPortOnly AddressPortStrategy = 1
|
|
||||||
AddressPortStrategy_SrvAddressOnly AddressPortStrategy = 2
|
|
||||||
AddressPortStrategy_SrvPortAndAddress AddressPortStrategy = 3
|
|
||||||
AddressPortStrategy_TxtPortOnly AddressPortStrategy = 4
|
|
||||||
AddressPortStrategy_TxtAddressOnly AddressPortStrategy = 5
|
|
||||||
AddressPortStrategy_TxtPortAndAddress AddressPortStrategy = 6
|
|
||||||
)
|
|
||||||
|
|
||||||
// Enum value maps for AddressPortStrategy.
|
|
||||||
var (
|
|
||||||
AddressPortStrategy_name = map[int32]string{
|
|
||||||
0: "None",
|
|
||||||
1: "SrvPortOnly",
|
|
||||||
2: "SrvAddressOnly",
|
|
||||||
3: "SrvPortAndAddress",
|
|
||||||
4: "TxtPortOnly",
|
|
||||||
5: "TxtAddressOnly",
|
|
||||||
6: "TxtPortAndAddress",
|
|
||||||
}
|
|
||||||
AddressPortStrategy_value = map[string]int32{
|
|
||||||
"None": 0,
|
|
||||||
"SrvPortOnly": 1,
|
|
||||||
"SrvAddressOnly": 2,
|
|
||||||
"SrvPortAndAddress": 3,
|
|
||||||
"TxtPortOnly": 4,
|
|
||||||
"TxtAddressOnly": 5,
|
|
||||||
"TxtPortAndAddress": 6,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (x AddressPortStrategy) Enum() *AddressPortStrategy {
|
|
||||||
p := new(AddressPortStrategy)
|
|
||||||
*p = x
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x AddressPortStrategy) String() string {
|
|
||||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (AddressPortStrategy) Descriptor() protoreflect.EnumDescriptor {
|
|
||||||
return file_transport_internet_config_proto_enumTypes[1].Descriptor()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (AddressPortStrategy) Type() protoreflect.EnumType {
|
|
||||||
return &file_transport_internet_config_proto_enumTypes[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x AddressPortStrategy) Number() protoreflect.EnumNumber {
|
|
||||||
return protoreflect.EnumNumber(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use AddressPortStrategy.Descriptor instead.
|
|
||||||
func (AddressPortStrategy) EnumDescriptor() ([]byte, []int) {
|
|
||||||
return file_transport_internet_config_proto_rawDescGZIP(), []int{1}
|
|
||||||
}
|
|
||||||
|
|
||||||
type SocketConfig_TProxyMode int32
|
type SocketConfig_TProxyMode int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -192,11 +131,11 @@ func (x SocketConfig_TProxyMode) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (SocketConfig_TProxyMode) Descriptor() protoreflect.EnumDescriptor {
|
func (SocketConfig_TProxyMode) Descriptor() protoreflect.EnumDescriptor {
|
||||||
return file_transport_internet_config_proto_enumTypes[2].Descriptor()
|
return file_transport_internet_config_proto_enumTypes[1].Descriptor()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (SocketConfig_TProxyMode) Type() protoreflect.EnumType {
|
func (SocketConfig_TProxyMode) Type() protoreflect.EnumType {
|
||||||
return &file_transport_internet_config_proto_enumTypes[2]
|
return &file_transport_internet_config_proto_enumTypes[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x SocketConfig_TProxyMode) Number() protoreflect.EnumNumber {
|
func (x SocketConfig_TProxyMode) Number() protoreflect.EnumNumber {
|
||||||
@ -417,12 +356,10 @@ type CustomSockopt struct {
|
|||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
System string `protobuf:"bytes,1,opt,name=system,proto3" json:"system,omitempty"`
|
Level string `protobuf:"bytes,1,opt,name=level,proto3" json:"level,omitempty"`
|
||||||
Network string `protobuf:"bytes,2,opt,name=network,proto3" json:"network,omitempty"`
|
Opt string `protobuf:"bytes,2,opt,name=opt,proto3" json:"opt,omitempty"`
|
||||||
Level string `protobuf:"bytes,3,opt,name=level,proto3" json:"level,omitempty"`
|
Value string `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"`
|
||||||
Opt string `protobuf:"bytes,4,opt,name=opt,proto3" json:"opt,omitempty"`
|
Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"`
|
||||||
Value string `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"`
|
|
||||||
Type string `protobuf:"bytes,6,opt,name=type,proto3" json:"type,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CustomSockopt) Reset() {
|
func (x *CustomSockopt) Reset() {
|
||||||
@ -455,20 +392,6 @@ func (*CustomSockopt) Descriptor() ([]byte, []int) {
|
|||||||
return file_transport_internet_config_proto_rawDescGZIP(), []int{3}
|
return file_transport_internet_config_proto_rawDescGZIP(), []int{3}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CustomSockopt) GetSystem() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.System
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *CustomSockopt) GetNetwork() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Network
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *CustomSockopt) GetLevel() string {
|
func (x *CustomSockopt) GetLevel() string {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Level
|
return x.Level
|
||||||
@ -528,7 +451,6 @@ type SocketConfig struct {
|
|||||||
Penetrate bool `protobuf:"varint,18,opt,name=penetrate,proto3" json:"penetrate,omitempty"`
|
Penetrate bool `protobuf:"varint,18,opt,name=penetrate,proto3" json:"penetrate,omitempty"`
|
||||||
TcpMptcp bool `protobuf:"varint,19,opt,name=tcp_mptcp,json=tcpMptcp,proto3" json:"tcp_mptcp,omitempty"`
|
TcpMptcp bool `protobuf:"varint,19,opt,name=tcp_mptcp,json=tcpMptcp,proto3" json:"tcp_mptcp,omitempty"`
|
||||||
CustomSockopt []*CustomSockopt `protobuf:"bytes,20,rep,name=customSockopt,proto3" json:"customSockopt,omitempty"`
|
CustomSockopt []*CustomSockopt `protobuf:"bytes,20,rep,name=customSockopt,proto3" json:"customSockopt,omitempty"`
|
||||||
AddressPortStrategy AddressPortStrategy `protobuf:"varint,21,opt,name=address_port_strategy,json=addressPortStrategy,proto3,enum=xray.transport.internet.AddressPortStrategy" json:"address_port_strategy,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *SocketConfig) Reset() {
|
func (x *SocketConfig) Reset() {
|
||||||
@ -701,13 +623,6 @@ func (x *SocketConfig) GetCustomSockopt() []*CustomSockopt {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *SocketConfig) GetAddressPortStrategy() AddressPortStrategy {
|
|
||||||
if x != nil {
|
|
||||||
return x.AddressPortStrategy
|
|
||||||
}
|
|
||||||
return AddressPortStrategy_None
|
|
||||||
}
|
|
||||||
|
|
||||||
var File_transport_internet_config_proto protoreflect.FileDescriptor
|
var File_transport_internet_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_transport_internet_config_proto_rawDesc = []byte{
|
var file_transport_internet_config_proto_rawDesc = []byte{
|
||||||
@ -756,108 +671,89 @@ var file_transport_internet_config_proto_rawDesc = []byte{
|
|||||||
0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x30, 0x0a, 0x13,
|
0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x30, 0x0a, 0x13,
|
||||||
0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x50, 0x72,
|
0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x50, 0x72,
|
||||||
0x6f, 0x78, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x74, 0x72, 0x61, 0x6e, 0x73,
|
0x6f, 0x78, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x74, 0x72, 0x61, 0x6e, 0x73,
|
||||||
0x70, 0x6f, 0x72, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x22, 0x93,
|
0x70, 0x6f, 0x72, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x22, 0x61,
|
||||||
0x01, 0x0a, 0x0d, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70, 0x74,
|
0x0a, 0x0d, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70, 0x74, 0x12,
|
||||||
0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
|
||||||
0x52, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77,
|
0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x10, 0x0a, 0x03, 0x6f, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01,
|
||||||
0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f,
|
0x28, 0x09, 0x52, 0x03, 0x6f, 0x70, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||||
0x72, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28,
|
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a,
|
||||||
0x09, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x10, 0x0a, 0x03, 0x6f, 0x70, 0x74, 0x18,
|
0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70,
|
||||||
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6f, 0x70, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,
|
0x65, 0x22, 0x9b, 0x07, 0x0a, 0x0c, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66,
|
||||||
0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
|
||||||
0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
|
0x52, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x66, 0x6f, 0x18, 0x02, 0x20,
|
||||||
0x74, 0x79, 0x70, 0x65, 0x22, 0xfd, 0x07, 0x0a, 0x0c, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43,
|
0x01, 0x28, 0x05, 0x52, 0x03, 0x74, 0x66, 0x6f, 0x12, 0x48, 0x0a, 0x06, 0x74, 0x70, 0x72, 0x6f,
|
||||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x01, 0x20,
|
0x78, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
|
||||||
0x01, 0x28, 0x05, 0x52, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x66, 0x6f,
|
0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x74, 0x66, 0x6f, 0x12, 0x48, 0x0a, 0x06, 0x74,
|
0x65, 0x74, 0x2e, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e,
|
||||||
0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x78, 0x72,
|
0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x06, 0x74, 0x70, 0x72, 0x6f,
|
||||||
0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74,
|
0x78, 0x79, 0x12, 0x41, 0x0a, 0x1d, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x72,
|
||||||
0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66,
|
0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72,
|
||||||
0x69, 0x67, 0x2e, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x06, 0x74,
|
0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x72, 0x65, 0x63, 0x65, 0x69,
|
||||||
0x70, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x41, 0x0a, 0x1d, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65,
|
0x76, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x73, 0x74, 0x41, 0x64,
|
||||||
0x5f, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x61,
|
0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x61, 0x64,
|
||||||
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x72, 0x65,
|
0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x62, 0x69, 0x6e,
|
||||||
0x63, 0x65, 0x69, 0x76, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x73,
|
0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x69, 0x6e, 0x64,
|
||||||
0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x69, 0x6e, 0x64,
|
0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x62, 0x69, 0x6e,
|
||||||
0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b,
|
0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f,
|
||||||
0x62, 0x69, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x62,
|
0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x07,
|
||||||
0x69, 0x6e, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08,
|
0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x50, 0x72, 0x6f, 0x78,
|
||||||
0x62, 0x69, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x61, 0x63, 0x63, 0x65,
|
0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x50, 0x0a, 0x0f, 0x64, 0x6f, 0x6d,
|
||||||
0x70, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
|
0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x08, 0x20, 0x01,
|
||||||
0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x50,
|
0x28, 0x0e, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70,
|
||||||
0x72, 0x6f, 0x78, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x50, 0x0a, 0x0f,
|
0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x44, 0x6f, 0x6d,
|
||||||
0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18,
|
0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d,
|
||||||
0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61,
|
0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x64,
|
||||||
0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e,
|
0x69, 0x61, 0x6c, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28,
|
||||||
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e,
|
0x09, 0x52, 0x0b, 0x64, 0x69, 0x61, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x35,
|
||||||
0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x21,
|
0x0a, 0x17, 0x74, 0x63, 0x70, 0x5f, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x61, 0x6c, 0x69, 0x76, 0x65,
|
||||||
0x0a, 0x0c, 0x64, 0x69, 0x61, 0x6c, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x09,
|
0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x61, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x78,
|
0x14, 0x74, 0x63, 0x70, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x49, 0x6e, 0x74,
|
||||||
0x79, 0x12, 0x35, 0x0a, 0x17, 0x74, 0x63, 0x70, 0x5f, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x61, 0x6c,
|
0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x2d, 0x0a, 0x13, 0x74, 0x63, 0x70, 0x5f, 0x6b, 0x65, 0x65,
|
||||||
0x69, 0x76, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x0a, 0x20, 0x01,
|
0x70, 0x5f, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x5f, 0x69, 0x64, 0x6c, 0x65, 0x18, 0x0b, 0x20, 0x01,
|
||||||
0x28, 0x05, 0x52, 0x14, 0x74, 0x63, 0x70, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65,
|
0x28, 0x05, 0x52, 0x10, 0x74, 0x63, 0x70, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65,
|
||||||
0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x2d, 0x0a, 0x13, 0x74, 0x63, 0x70, 0x5f,
|
0x49, 0x64, 0x6c, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x63, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x67,
|
||||||
0x6b, 0x65, 0x65, 0x70, 0x5f, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x5f, 0x69, 0x64, 0x6c, 0x65, 0x18,
|
0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x63,
|
||||||
0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x74, 0x63, 0x70, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c,
|
0x70, 0x43, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x69,
|
||||||
0x69, 0x76, 0x65, 0x49, 0x64, 0x6c, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x63, 0x70, 0x5f, 0x63,
|
0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
|
||||||
0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52,
|
0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x36, 0x6f,
|
||||||
0x0d, 0x74, 0x63, 0x70, 0x43, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c,
|
0x6e, 0x6c, 0x79, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x76, 0x36, 0x6f, 0x6e, 0x6c,
|
||||||
0x0a, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28,
|
0x79, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x63, 0x70, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x5f,
|
||||||
0x09, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06,
|
0x63, 0x6c, 0x61, 0x6d, 0x70, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x74, 0x63, 0x70,
|
||||||
0x76, 0x36, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x76, 0x36,
|
0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x43, 0x6c, 0x61, 0x6d, 0x70, 0x12, 0x28, 0x0a, 0x10, 0x74,
|
||||||
0x6f, 0x6e, 0x6c, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x63, 0x70, 0x5f, 0x77, 0x69, 0x6e, 0x64,
|
0x63, 0x70, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18,
|
||||||
0x6f, 0x77, 0x5f, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e,
|
0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x74, 0x63, 0x70, 0x55, 0x73, 0x65, 0x72, 0x54, 0x69,
|
||||||
0x74, 0x63, 0x70, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x43, 0x6c, 0x61, 0x6d, 0x70, 0x12, 0x28,
|
0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x74, 0x63, 0x70, 0x5f, 0x6d, 0x61, 0x78,
|
||||||
0x0a, 0x10, 0x74, 0x63, 0x70, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f,
|
0x5f, 0x73, 0x65, 0x67, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x63, 0x70, 0x4d,
|
||||||
0x75, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x74, 0x63, 0x70, 0x55, 0x73, 0x65,
|
0x61, 0x78, 0x53, 0x65, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x65, 0x6e, 0x65, 0x74, 0x72, 0x61,
|
||||||
0x72, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x74, 0x63, 0x70, 0x5f,
|
0x74, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x70, 0x65, 0x6e, 0x65, 0x74, 0x72,
|
||||||
0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, 0x67, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74,
|
0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x63, 0x70, 0x5f, 0x6d, 0x70, 0x74, 0x63, 0x70,
|
||||||
0x63, 0x70, 0x4d, 0x61, 0x78, 0x53, 0x65, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x65, 0x6e, 0x65,
|
0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x74, 0x63, 0x70, 0x4d, 0x70, 0x74, 0x63, 0x70,
|
||||||
0x74, 0x72, 0x61, 0x74, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x70, 0x65, 0x6e,
|
0x12, 0x4c, 0x0a, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70,
|
||||||
0x65, 0x74, 0x72, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x63, 0x70, 0x5f, 0x6d, 0x70,
|
0x74, 0x18, 0x14, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74,
|
||||||
0x74, 0x63, 0x70, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x74, 0x63, 0x70, 0x4d, 0x70,
|
0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65,
|
||||||
0x74, 0x63, 0x70, 0x12, 0x4c, 0x0a, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63,
|
0x74, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70, 0x74, 0x52,
|
||||||
0x6b, 0x6f, 0x70, 0x74, 0x18, 0x14, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x78, 0x72, 0x61,
|
0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70, 0x74, 0x22, 0x2f,
|
||||||
0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65,
|
0x0a, 0x0a, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x07, 0x0a, 0x03,
|
||||||
0x72, 0x6e, 0x65, 0x74, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f,
|
0x4f, 0x66, 0x66, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x10,
|
||||||
0x70, 0x74, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70,
|
0x01, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x10, 0x02, 0x2a,
|
||||||
0x74, 0x12, 0x60, 0x0a, 0x15, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x6f, 0x72,
|
0xa9, 0x01, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65,
|
||||||
0x74, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0e,
|
0x67, 0x79, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x53, 0x5f, 0x49, 0x53, 0x10, 0x00, 0x12, 0x0a, 0x0a,
|
||||||
0x32, 0x2c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
|
0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45,
|
||||||
0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65,
|
0x5f, 0x49, 0x50, 0x34, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50,
|
||||||
0x73, 0x73, 0x50, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x13,
|
0x36, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x36, 0x10,
|
||||||
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x50, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74,
|
0x04, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x34, 0x10, 0x05, 0x12,
|
||||||
0x65, 0x67, 0x79, 0x22, 0x2f, 0x0a, 0x0a, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64,
|
0x0c, 0x0a, 0x08, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x06, 0x12, 0x0d, 0x0a,
|
||||||
0x65, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x66, 0x66, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x54, 0x50,
|
0x09, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x07, 0x12, 0x0d, 0x0a, 0x09,
|
||||||
0x72, 0x6f, 0x78, 0x79, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65,
|
0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x08, 0x12, 0x0e, 0x0a, 0x0a, 0x46,
|
||||||
0x63, 0x74, 0x10, 0x02, 0x2a, 0xa9, 0x01, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53,
|
0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x36, 0x10, 0x09, 0x12, 0x0e, 0x0a, 0x0a, 0x46,
|
||||||
0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x53, 0x5f, 0x49, 0x53,
|
0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x34, 0x10, 0x0a, 0x42, 0x67, 0x0a, 0x1b, 0x63,
|
||||||
0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x01, 0x12, 0x0b,
|
0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
|
||||||
0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55,
|
0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69,
|
||||||
0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x53, 0x45, 0x5f,
|
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72,
|
||||||
0x49, 0x50, 0x34, 0x36, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50,
|
0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
|
||||||
0x36, 0x34, 0x10, 0x05, 0x12, 0x0c, 0x0a, 0x08, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50,
|
0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0xaa, 0x02, 0x17, 0x58, 0x72, 0x61,
|
||||||
0x10, 0x06, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10,
|
0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65,
|
||||||
0x07, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x08,
|
0x72, 0x6e, 0x65, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x36, 0x10, 0x09,
|
|
||||||
0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x34, 0x10, 0x0a,
|
|
||||||
0x2a, 0x97, 0x01, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x50, 0x6f, 0x72, 0x74,
|
|
||||||
0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x6f, 0x6e, 0x65,
|
|
||||||
0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x72, 0x76, 0x50, 0x6f, 0x72, 0x74, 0x4f, 0x6e, 0x6c,
|
|
||||||
0x79, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x72, 0x76, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
|
|
||||||
0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x72, 0x76, 0x50, 0x6f,
|
|
||||||
0x72, 0x74, 0x41, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0x03, 0x12, 0x0f,
|
|
||||||
0x0a, 0x0b, 0x54, 0x78, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x4f, 0x6e, 0x6c, 0x79, 0x10, 0x04, 0x12,
|
|
||||||
0x12, 0x0a, 0x0e, 0x54, 0x78, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x4f, 0x6e, 0x6c,
|
|
||||||
0x79, 0x10, 0x05, 0x12, 0x15, 0x0a, 0x11, 0x54, 0x78, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x41, 0x6e,
|
|
||||||
0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0x06, 0x42, 0x67, 0x0a, 0x1b, 0x63, 0x6f,
|
|
||||||
0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74,
|
|
||||||
0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69, 0x74,
|
|
||||||
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61,
|
|
||||||
0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74,
|
|
||||||
0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0xaa, 0x02, 0x17, 0x58, 0x72, 0x61, 0x79,
|
|
||||||
0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72,
|
|
||||||
0x6e, 0x65, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -872,35 +768,33 @@ func file_transport_internet_config_proto_rawDescGZIP() []byte {
|
|||||||
return file_transport_internet_config_proto_rawDescData
|
return file_transport_internet_config_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_transport_internet_config_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
|
var file_transport_internet_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||||
var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||||
var file_transport_internet_config_proto_goTypes = []any{
|
var file_transport_internet_config_proto_goTypes = []any{
|
||||||
(DomainStrategy)(0), // 0: xray.transport.internet.DomainStrategy
|
(DomainStrategy)(0), // 0: xray.transport.internet.DomainStrategy
|
||||||
(AddressPortStrategy)(0), // 1: xray.transport.internet.AddressPortStrategy
|
(SocketConfig_TProxyMode)(0), // 1: xray.transport.internet.SocketConfig.TProxyMode
|
||||||
(SocketConfig_TProxyMode)(0), // 2: xray.transport.internet.SocketConfig.TProxyMode
|
(*TransportConfig)(nil), // 2: xray.transport.internet.TransportConfig
|
||||||
(*TransportConfig)(nil), // 3: xray.transport.internet.TransportConfig
|
(*StreamConfig)(nil), // 3: xray.transport.internet.StreamConfig
|
||||||
(*StreamConfig)(nil), // 4: xray.transport.internet.StreamConfig
|
(*ProxyConfig)(nil), // 4: xray.transport.internet.ProxyConfig
|
||||||
(*ProxyConfig)(nil), // 5: xray.transport.internet.ProxyConfig
|
(*CustomSockopt)(nil), // 5: xray.transport.internet.CustomSockopt
|
||||||
(*CustomSockopt)(nil), // 6: xray.transport.internet.CustomSockopt
|
(*SocketConfig)(nil), // 6: xray.transport.internet.SocketConfig
|
||||||
(*SocketConfig)(nil), // 7: xray.transport.internet.SocketConfig
|
(*serial.TypedMessage)(nil), // 7: xray.common.serial.TypedMessage
|
||||||
(*serial.TypedMessage)(nil), // 8: xray.common.serial.TypedMessage
|
(*net.IPOrDomain)(nil), // 8: xray.common.net.IPOrDomain
|
||||||
(*net.IPOrDomain)(nil), // 9: xray.common.net.IPOrDomain
|
|
||||||
}
|
}
|
||||||
var file_transport_internet_config_proto_depIdxs = []int32{
|
var file_transport_internet_config_proto_depIdxs = []int32{
|
||||||
8, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage
|
7, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage
|
||||||
9, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain
|
8, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain
|
||||||
3, // 2: xray.transport.internet.StreamConfig.transport_settings:type_name -> xray.transport.internet.TransportConfig
|
2, // 2: xray.transport.internet.StreamConfig.transport_settings:type_name -> xray.transport.internet.TransportConfig
|
||||||
8, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage
|
7, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage
|
||||||
7, // 4: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
|
6, // 4: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
|
||||||
2, // 5: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
|
1, // 5: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
|
||||||
0, // 6: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
|
0, // 6: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
|
||||||
6, // 7: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
|
5, // 7: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
|
||||||
1, // 8: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy
|
8, // [8:8] is the sub-list for method output_type
|
||||||
9, // [9:9] is the sub-list for method output_type
|
8, // [8:8] is the sub-list for method input_type
|
||||||
9, // [9:9] is the sub-list for method input_type
|
8, // [8:8] is the sub-list for extension type_name
|
||||||
9, // [9:9] is the sub-list for extension type_name
|
8, // [8:8] is the sub-list for extension extendee
|
||||||
9, // [9:9] is the sub-list for extension extendee
|
0, // [0:8] is the sub-list for field type_name
|
||||||
0, // [0:9] is the sub-list for field type_name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_transport_internet_config_proto_init() }
|
func init() { file_transport_internet_config_proto_init() }
|
||||||
@ -913,7 +807,7 @@ func file_transport_internet_config_proto_init() {
|
|||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_transport_internet_config_proto_rawDesc,
|
RawDescriptor: file_transport_internet_config_proto_rawDesc,
|
||||||
NumEnums: 3,
|
NumEnums: 2,
|
||||||
NumMessages: 5,
|
NumMessages: 5,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
|
@ -23,16 +23,6 @@ enum DomainStrategy {
|
|||||||
FORCE_IP64 = 10;
|
FORCE_IP64 = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AddressPortStrategy {
|
|
||||||
None = 0;
|
|
||||||
SrvPortOnly = 1;
|
|
||||||
SrvAddressOnly = 2;
|
|
||||||
SrvPortAndAddress = 3;
|
|
||||||
TxtPortOnly = 4;
|
|
||||||
TxtAddressOnly = 5;
|
|
||||||
TxtPortAndAddress = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TransportConfig {
|
message TransportConfig {
|
||||||
// Transport protocol name.
|
// Transport protocol name.
|
||||||
string protocol_name = 3;
|
string protocol_name = 3;
|
||||||
@ -65,12 +55,10 @@ message ProxyConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message CustomSockopt {
|
message CustomSockopt {
|
||||||
string system = 1;
|
string level = 1;
|
||||||
string network = 2;
|
string opt = 2;
|
||||||
string level = 3;
|
string value = 3;
|
||||||
string opt = 4;
|
string type = 4;
|
||||||
string value = 5;
|
|
||||||
string type = 6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SocketConfig is options to be applied on network sockets.
|
// SocketConfig is options to be applied on network sockets.
|
||||||
@ -128,6 +116,4 @@ message SocketConfig {
|
|||||||
bool tcp_mptcp = 19;
|
bool tcp_mptcp = 19;
|
||||||
|
|
||||||
repeated CustomSockopt customSockopt = 20;
|
repeated CustomSockopt customSockopt = 20;
|
||||||
|
|
||||||
AddressPortStrategy address_port_strategy = 21;
|
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,6 @@ package internet
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
gonet "net"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/dice"
|
"github.com/xtls/xray-core/common/dice"
|
||||||
@ -87,44 +84,42 @@ var (
|
|||||||
|
|
||||||
func lookupIP(domain string, strategy DomainStrategy, localAddr net.Address) ([]net.IP, error) {
|
func lookupIP(domain string, strategy DomainStrategy, localAddr net.Address) ([]net.IP, error) {
|
||||||
if dnsClient == nil {
|
if dnsClient == nil {
|
||||||
return nil, errors.New("DNS client not initialized").AtError()
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ips, _, err := dnsClient.LookupIP(domain, dns.IPOption{
|
ips, err := dnsClient.LookupIP(domain, dns.IPOption{
|
||||||
IPv4Enable: (localAddr == nil || localAddr.Family().IsIPv4()) && strategy.preferIP4(),
|
IPv4Enable: (localAddr == nil || localAddr.Family().IsIPv4()) && strategy.preferIP4(),
|
||||||
IPv6Enable: (localAddr == nil || localAddr.Family().IsIPv6()) && strategy.preferIP6(),
|
IPv6Enable: (localAddr == nil || localAddr.Family().IsIPv6()) && strategy.preferIP6(),
|
||||||
})
|
})
|
||||||
{ // Resolve fallback
|
{ // Resolve fallback
|
||||||
if (len(ips) == 0 || err != nil) && strategy.hasFallback() && localAddr == nil {
|
if (len(ips) == 0 || err != nil) && strategy.hasFallback() && localAddr == nil {
|
||||||
ips, _, err = dnsClient.LookupIP(domain, dns.IPOption{
|
ips, err = dnsClient.LookupIP(domain, dns.IPOption{
|
||||||
IPv4Enable: strategy.fallbackIP4(),
|
IPv4Enable: strategy.fallbackIP4(),
|
||||||
IPv6Enable: strategy.fallbackIP6(),
|
IPv6Enable: strategy.fallbackIP6(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil && len(ips) == 0 {
|
|
||||||
return nil, dns.ErrEmptyResponse
|
|
||||||
}
|
|
||||||
return ips, err
|
return ips, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func canLookupIP(dst net.Destination, sockopt *SocketConfig) bool {
|
func canLookupIP(ctx context.Context, dst net.Destination, sockopt *SocketConfig) bool {
|
||||||
if dst.Address.Family().IsIP() {
|
if dst.Address.Family().IsIP() || dnsClient == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return sockopt.DomainStrategy.hasStrategy()
|
return sockopt.DomainStrategy.hasStrategy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func redirect(ctx context.Context, dst net.Destination, obt string, h outbound.Handler) net.Conn {
|
func redirect(ctx context.Context, dst net.Destination, obt string) net.Conn {
|
||||||
errors.LogInfo(ctx, "redirecting request "+dst.String()+" to "+obt)
|
errors.LogInfo(ctx, "redirecting request "+dst.String()+" to "+obt)
|
||||||
|
h := obm.GetHandler(obt)
|
||||||
outbounds := session.OutboundsFromContext(ctx)
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
ctx = session.ContextWithOutbounds(ctx, append(outbounds, &session.Outbound{
|
ctx = session.ContextWithOutbounds(ctx, append(outbounds, &session.Outbound{
|
||||||
Target: dst,
|
Target: dst,
|
||||||
Gateway: nil,
|
Gateway: nil,
|
||||||
Tag: obt,
|
Tag: obt,
|
||||||
})) // add another outbound in session ctx
|
})) // add another outbound in session ctx
|
||||||
|
if h != nil {
|
||||||
ur, uw := pipe.New(pipe.OptionsFromContext(ctx)...)
|
ur, uw := pipe.New(pipe.OptionsFromContext(ctx)...)
|
||||||
dr, dw := pipe.New(pipe.OptionsFromContext(ctx)...)
|
dr, dw := pipe.New(pipe.OptionsFromContext(ctx)...)
|
||||||
|
|
||||||
@ -141,94 +136,8 @@ func redirect(ctx context.Context, dst net.Destination, obt string, h outbound.H
|
|||||||
cnc.ConnectionOnClose(common.ChainedClosable{uw, dw}),
|
cnc.ConnectionOnClose(common.ChainedClosable{uw, dw}),
|
||||||
)
|
)
|
||||||
return nc
|
return nc
|
||||||
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
func checkAddressPortStrategy(ctx context.Context, dest net.Destination, sockopt *SocketConfig) (*net.Destination, error) {
|
|
||||||
if sockopt.AddressPortStrategy == AddressPortStrategy_None {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
newDest := dest
|
|
||||||
var OverridePort, OverrideAddress bool
|
|
||||||
var OverrideBy string
|
|
||||||
switch sockopt.AddressPortStrategy {
|
|
||||||
case AddressPortStrategy_SrvPortOnly:
|
|
||||||
OverridePort = true
|
|
||||||
OverrideAddress = false
|
|
||||||
OverrideBy = "srv"
|
|
||||||
case AddressPortStrategy_SrvAddressOnly:
|
|
||||||
OverridePort = false
|
|
||||||
OverrideAddress = true
|
|
||||||
OverrideBy = "srv"
|
|
||||||
case AddressPortStrategy_SrvPortAndAddress:
|
|
||||||
OverridePort = true
|
|
||||||
OverrideAddress = true
|
|
||||||
OverrideBy = "srv"
|
|
||||||
case AddressPortStrategy_TxtPortOnly:
|
|
||||||
OverridePort = true
|
|
||||||
OverrideAddress = false
|
|
||||||
OverrideBy = "txt"
|
|
||||||
case AddressPortStrategy_TxtAddressOnly:
|
|
||||||
OverridePort = false
|
|
||||||
OverrideAddress = true
|
|
||||||
OverrideBy = "txt"
|
|
||||||
case AddressPortStrategy_TxtPortAndAddress:
|
|
||||||
OverridePort = true
|
|
||||||
OverrideAddress = true
|
|
||||||
OverrideBy = "txt"
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unknown AddressPortStrategy")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !dest.Address.Family().IsDomain() {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if OverrideBy == "srv" {
|
|
||||||
errors.LogDebug(ctx, "query SRV record for "+dest.Address.String())
|
|
||||||
parts := strings.SplitN(dest.Address.String(), ".", 3)
|
|
||||||
if len(parts) != 3 {
|
|
||||||
return nil, errors.New("invalid address format", dest.Address.String())
|
|
||||||
}
|
|
||||||
_, srvRecords, err := gonet.DefaultResolver.LookupSRV(context.Background(), parts[0][1:], parts[1][1:], parts[2])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("failed to lookup SRV record").Base(err)
|
|
||||||
}
|
|
||||||
errors.LogDebug(ctx, "SRV record: "+fmt.Sprintf("addr=%s, port=%d, priority=%d, weight=%d", srvRecords[0].Target, srvRecords[0].Port, srvRecords[0].Priority, srvRecords[0].Weight))
|
|
||||||
if OverridePort {
|
|
||||||
newDest.Port = net.Port(srvRecords[0].Port)
|
|
||||||
}
|
|
||||||
if OverrideAddress {
|
|
||||||
newDest.Address = net.ParseAddress(srvRecords[0].Target)
|
|
||||||
}
|
|
||||||
return &newDest, nil
|
|
||||||
}
|
|
||||||
if OverrideBy == "txt" {
|
|
||||||
errors.LogDebug(ctx, "query TXT record for "+dest.Address.String())
|
|
||||||
txtRecords, err := gonet.DefaultResolver.LookupTXT(ctx, dest.Address.String())
|
|
||||||
if err != nil {
|
|
||||||
errors.LogError(ctx, "failed to lookup SRV record: "+err.Error())
|
|
||||||
return nil, errors.New("failed to lookup SRV record").Base(err)
|
|
||||||
}
|
|
||||||
for _, txtRecord := range txtRecords {
|
|
||||||
errors.LogDebug(ctx, "TXT record: "+txtRecord)
|
|
||||||
addr_s, port_s, _ := net.SplitHostPort(string(txtRecord))
|
|
||||||
addr := net.ParseAddress(addr_s)
|
|
||||||
port, err := net.PortFromString(port_s)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if OverridePort {
|
|
||||||
newDest.Port = port
|
|
||||||
}
|
|
||||||
if OverrideAddress {
|
|
||||||
newDest.Address = addr
|
|
||||||
}
|
|
||||||
return &newDest, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialSystem calls system dialer to create a network connection.
|
// DialSystem calls system dialer to create a network connection.
|
||||||
@ -243,33 +152,21 @@ func DialSystem(ctx context.Context, dest net.Destination, sockopt *SocketConfig
|
|||||||
return effectiveSystemDialer.Dial(ctx, src, dest, sockopt)
|
return effectiveSystemDialer.Dial(ctx, src, dest, sockopt)
|
||||||
}
|
}
|
||||||
|
|
||||||
if newDest, err := checkAddressPortStrategy(ctx, dest, sockopt); err == nil && newDest != nil {
|
if canLookupIP(ctx, dest, sockopt) {
|
||||||
errors.LogInfo(ctx, "replace destination with "+newDest.String())
|
|
||||||
dest = *newDest
|
|
||||||
}
|
|
||||||
|
|
||||||
if canLookupIP(dest, sockopt) {
|
|
||||||
ips, err := lookupIP(dest.Address.String(), sockopt.DomainStrategy, src)
|
ips, err := lookupIP(dest.Address.String(), sockopt.DomainStrategy, src)
|
||||||
if err != nil {
|
if err == nil && len(ips) > 0 {
|
||||||
errors.LogErrorInner(ctx, err, "failed to resolve ip")
|
|
||||||
if sockopt.DomainStrategy.forceIP() {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dest.Address = net.IPAddress(ips[dice.Roll(len(ips))])
|
dest.Address = net.IPAddress(ips[dice.Roll(len(ips))])
|
||||||
errors.LogInfo(ctx, "replace destination with "+dest.String())
|
errors.LogInfo(ctx, "replace destination with "+dest.String())
|
||||||
|
} else if err != nil {
|
||||||
|
errors.LogWarningInner(ctx, err, "failed to resolve ip")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sockopt.DialerProxy) > 0 {
|
if obm != nil && len(sockopt.DialerProxy) > 0 {
|
||||||
if obm == nil {
|
nc := redirect(ctx, dest, sockopt.DialerProxy)
|
||||||
return nil, errors.New("there is no outbound manager for dialerProxy").AtError()
|
if nc != nil {
|
||||||
|
return nc, nil
|
||||||
}
|
}
|
||||||
h := obm.GetHandler(sockopt.DialerProxy)
|
|
||||||
if h == nil {
|
|
||||||
return nil, errors.New("there is no outbound handler for dialerProxy").AtError()
|
|
||||||
}
|
|
||||||
return redirect(ctx, dest, sockopt.DialerProxy, h), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return effectiveSystemDialer.Dial(ctx, src, dest, sockopt)
|
return effectiveSystemDialer.Dial(ctx, src, dest, sockopt)
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"crypto/ecdh"
|
"crypto/ecdh"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
gotls "crypto/tls"
|
gotls "crypto/tls"
|
||||||
@ -15,6 +16,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -25,15 +27,18 @@ import (
|
|||||||
|
|
||||||
utls "github.com/refraction-networking/utls"
|
utls "github.com/refraction-networking/utls"
|
||||||
"github.com/xtls/reality"
|
"github.com/xtls/reality"
|
||||||
"github.com/xtls/xray-core/common/crypto"
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/transport/internet/tls"
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
"golang.org/x/crypto/hkdf"
|
"golang.org/x/crypto/hkdf"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:linkname aesgcmPreferred github.com/refraction-networking/utls.aesgcmPreferred
|
||||||
|
func aesgcmPreferred(ciphers []uint16) bool
|
||||||
|
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
*reality.Conn
|
*reality.Conn
|
||||||
}
|
}
|
||||||
@ -133,18 +138,23 @@ func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destinati
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("REALITY: publicKey == nil")
|
return nil, errors.New("REALITY: publicKey == nil")
|
||||||
}
|
}
|
||||||
if uConn.HandshakeState.State13.KeyShareKeys.Ecdhe == nil {
|
if uConn.HandshakeState.State13.EcdheKey == nil {
|
||||||
return nil, errors.New("Current fingerprint ", uConn.ClientHelloID.Client, uConn.ClientHelloID.Version, " does not support TLS 1.3, REALITY handshake cannot establish.")
|
return nil, errors.New("Current fingerprint ", uConn.ClientHelloID.Client, uConn.ClientHelloID.Version, " does not support TLS 1.3, REALITY handshake cannot establish.")
|
||||||
}
|
}
|
||||||
uConn.AuthKey, _ = uConn.HandshakeState.State13.KeyShareKeys.Ecdhe.ECDH(publicKey)
|
uConn.AuthKey, _ = uConn.HandshakeState.State13.EcdheKey.ECDH(publicKey)
|
||||||
if uConn.AuthKey == nil {
|
if uConn.AuthKey == nil {
|
||||||
return nil, errors.New("REALITY: SharedKey == nil")
|
return nil, errors.New("REALITY: SharedKey == nil")
|
||||||
}
|
}
|
||||||
if _, err := hkdf.New(sha256.New, uConn.AuthKey, hello.Random[:20], []byte("REALITY")).Read(uConn.AuthKey); err != nil {
|
if _, err := hkdf.New(sha256.New, uConn.AuthKey, hello.Random[:20], []byte("REALITY")).Read(uConn.AuthKey); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
var aead cipher.AEAD
|
||||||
|
if aesgcmPreferred(hello.CipherSuites) {
|
||||||
block, _ := aes.NewCipher(uConn.AuthKey)
|
block, _ := aes.NewCipher(uConn.AuthKey)
|
||||||
aead, _ := cipher.NewGCM(block)
|
aead, _ = cipher.NewGCM(block)
|
||||||
|
} else {
|
||||||
|
aead, _ = chacha20poly1305.New(uConn.AuthKey)
|
||||||
|
}
|
||||||
if config.Show {
|
if config.Show {
|
||||||
errors.LogInfo(ctx, fmt.Sprintf("REALITY localAddr: %v\tuConn.AuthKey[:16]: %v\tAEAD: %T\n", localAddr, uConn.AuthKey[:16], aead))
|
errors.LogInfo(ctx, fmt.Sprintf("REALITY localAddr: %v\tuConn.AuthKey[:16]: %v\tAEAD: %T\n", localAddr, uConn.AuthKey[:16], aead))
|
||||||
}
|
}
|
||||||
@ -203,13 +213,13 @@ func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destinati
|
|||||||
}
|
}
|
||||||
times := 1
|
times := 1
|
||||||
if !first {
|
if !first {
|
||||||
times = int(crypto.RandBetween(config.SpiderY[4], config.SpiderY[5]))
|
times = int(randBetween(config.SpiderY[4], config.SpiderY[5]))
|
||||||
}
|
}
|
||||||
for j := 0; j < times; j++ {
|
for j := 0; j < times; j++ {
|
||||||
if !first && j == 0 {
|
if !first && j == 0 {
|
||||||
req.Header.Set("Referer", firstURL)
|
req.Header.Set("Referer", firstURL)
|
||||||
}
|
}
|
||||||
req.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", int(crypto.RandBetween(config.SpiderY[0], config.SpiderY[1])))})
|
req.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", int(randBetween(config.SpiderY[0], config.SpiderY[1])))})
|
||||||
if resp, err = client.Do(req); err != nil {
|
if resp, err = client.Do(req); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -233,18 +243,18 @@ func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destinati
|
|||||||
}
|
}
|
||||||
maps.Unlock()
|
maps.Unlock()
|
||||||
if !first {
|
if !first {
|
||||||
time.Sleep(time.Duration(crypto.RandBetween(config.SpiderY[6], config.SpiderY[7])) * time.Millisecond) // interval
|
time.Sleep(time.Duration(randBetween(config.SpiderY[6], config.SpiderY[7])) * time.Millisecond) // interval
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
get(true)
|
get(true)
|
||||||
concurrency := int(crypto.RandBetween(config.SpiderY[2], config.SpiderY[3]))
|
concurrency := int(randBetween(config.SpiderY[2], config.SpiderY[3]))
|
||||||
for i := 0; i < concurrency; i++ {
|
for i := 0; i < concurrency; i++ {
|
||||||
go get(false)
|
go get(false)
|
||||||
}
|
}
|
||||||
// Do not close the connection
|
// Do not close the connection
|
||||||
}()
|
}()
|
||||||
time.Sleep(time.Duration(crypto.RandBetween(config.SpiderY[8], config.SpiderY[9])) * time.Millisecond) // return
|
time.Sleep(time.Duration(randBetween(config.SpiderY[8], config.SpiderY[9])) * time.Millisecond) // return
|
||||||
return nil, errors.New("REALITY: processed invalid connection").AtWarning()
|
return nil, errors.New("REALITY: processed invalid connection").AtWarning()
|
||||||
}
|
}
|
||||||
return uConn, nil
|
return uConn, nil
|
||||||
@ -261,7 +271,7 @@ var maps struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getPathLocked(paths map[string]struct{}) string {
|
func getPathLocked(paths map[string]struct{}) string {
|
||||||
stopAt := int(crypto.RandBetween(0, int64(len(paths)-1)))
|
stopAt := int(randBetween(0, int64(len(paths)-1)))
|
||||||
i := 0
|
i := 0
|
||||||
for s := range paths {
|
for s := range paths {
|
||||||
if i == stopAt {
|
if i == stopAt {
|
||||||
@ -271,3 +281,11 @@ func getPathLocked(paths map[string]struct{}) string {
|
|||||||
}
|
}
|
||||||
return "/"
|
return "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func randBetween(left int64, right int64) int64 {
|
||||||
|
if left == right {
|
||||||
|
return left
|
||||||
|
}
|
||||||
|
bigInt, _ := rand.Int(rand.Reader, big.NewInt(right-left))
|
||||||
|
return left + bigInt.Int64()
|
||||||
|
}
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
package internet
|
package internet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
network "net"
|
||||||
gonet "net"
|
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@ -112,6 +108,14 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if config.Interface != "" {
|
||||||
|
InterfaceIndex := getInterfaceIndexByName(config.Interface)
|
||||||
|
if InterfaceIndex != 0 {
|
||||||
|
if err := unix.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, InterfaceIndex); err != nil {
|
||||||
|
return errors.New("failed to set Interface").Base(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if config.TcpKeepAliveIdle > 0 || config.TcpKeepAliveInterval > 0 {
|
if config.TcpKeepAliveIdle > 0 || config.TcpKeepAliveInterval > 0 {
|
||||||
if config.TcpKeepAliveIdle > 0 {
|
if config.TcpKeepAliveIdle > 0 {
|
||||||
@ -134,61 +138,6 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Interface != "" {
|
|
||||||
iface, err := gonet.InterfaceByName(config.Interface)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("failed to get interface ", config.Interface).Base(err)
|
|
||||||
}
|
|
||||||
if network == "tcp6" || network == "udp6" {
|
|
||||||
if err := unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, iface.Index); err != nil {
|
|
||||||
return errors.New("failed to set IPV6_BOUND_IF").Base(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index); err != nil {
|
|
||||||
return errors.New("failed to set IP_BOUND_IF").Base(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(config.CustomSockopt) > 0 {
|
|
||||||
for _, custom := range config.CustomSockopt {
|
|
||||||
if custom.System != "" && custom.System != runtime.GOOS {
|
|
||||||
errors.LogDebug(context.Background(), "CustomSockopt system not match: ", "want ", custom.System, " got ", runtime.GOOS)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Skip unwanted network type
|
|
||||||
// network might be tcp4 or tcp6
|
|
||||||
// use HasPrefix so that "tcp" can match tcp4/6 with "tcp" if user want to control all tcp (udp is also the same)
|
|
||||||
// if it is empty, strings.HasPrefix will always return true to make it apply for all networks
|
|
||||||
if !strings.HasPrefix(network, custom.Network) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var level = 0x6 // default TCP
|
|
||||||
var opt int
|
|
||||||
if len(custom.Opt) == 0 {
|
|
||||||
return errors.New("No opt!")
|
|
||||||
} else {
|
|
||||||
opt, _ = strconv.Atoi(custom.Opt)
|
|
||||||
}
|
|
||||||
if custom.Level != "" {
|
|
||||||
level, _ = strconv.Atoi(custom.Level)
|
|
||||||
}
|
|
||||||
if custom.Type == "int" {
|
|
||||||
value, _ := strconv.Atoi(custom.Value)
|
|
||||||
if err := syscall.SetsockoptInt(int(fd), level, opt, value); err != nil {
|
|
||||||
return errors.New("failed to set CustomSockoptInt", opt, value, err)
|
|
||||||
}
|
|
||||||
} else if custom.Type == "str" {
|
|
||||||
if err := syscall.SetsockoptString(int(fd), level, opt, custom.Value); err != nil {
|
|
||||||
return errors.New("failed to set CustomSockoptString", opt, custom.Value, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return errors.New("unknown CustomSockopt type:", custom.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,6 +152,14 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if config.Interface != "" {
|
||||||
|
InterfaceIndex := getInterfaceIndexByName(config.Interface)
|
||||||
|
if InterfaceIndex != 0 {
|
||||||
|
if err := unix.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, InterfaceIndex); err != nil {
|
||||||
|
return errors.New("failed to set Interface").Base(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if config.TcpKeepAliveIdle > 0 || config.TcpKeepAliveInterval > 0 {
|
if config.TcpKeepAliveIdle > 0 || config.TcpKeepAliveInterval > 0 {
|
||||||
if config.TcpKeepAliveIdle > 0 {
|
if config.TcpKeepAliveIdle > 0 {
|
||||||
@ -225,67 +182,6 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Interface != "" {
|
|
||||||
iface, err := gonet.InterfaceByName(config.Interface)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("failed to get interface ", config.Interface).Base(err)
|
|
||||||
}
|
|
||||||
if network == "tcp6" || network == "udp6" {
|
|
||||||
if err := unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, iface.Index); err != nil {
|
|
||||||
return errors.New("failed to set IPV6_BOUND_IF").Base(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index); err != nil {
|
|
||||||
return errors.New("failed to set IP_BOUND_IF").Base(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.V6Only {
|
|
||||||
if err := unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_V6ONLY, 1); err != nil {
|
|
||||||
return errors.New("failed to set IPV6_V6ONLY").Base(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(config.CustomSockopt) > 0 {
|
|
||||||
for _, custom := range config.CustomSockopt {
|
|
||||||
if custom.System != "" && custom.System != runtime.GOOS {
|
|
||||||
errors.LogDebug(context.Background(), "CustomSockopt system not match: ", "want ", custom.System, " got ", runtime.GOOS)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Skip unwanted network type
|
|
||||||
// network might be tcp4 or tcp6
|
|
||||||
// use HasPrefix so that "tcp" can match tcp4/6 with "tcp" if user want to control all tcp (udp is also the same)
|
|
||||||
// if it is empty, strings.HasPrefix will always return true to make it apply for all networks
|
|
||||||
if !strings.HasPrefix(network, custom.Network) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var level = 0x6 // default TCP
|
|
||||||
var opt int
|
|
||||||
if len(custom.Opt) == 0 {
|
|
||||||
return errors.New("No opt!")
|
|
||||||
} else {
|
|
||||||
opt, _ = strconv.Atoi(custom.Opt)
|
|
||||||
}
|
|
||||||
if custom.Level != "" {
|
|
||||||
level, _ = strconv.Atoi(custom.Level)
|
|
||||||
}
|
|
||||||
if custom.Type == "int" {
|
|
||||||
value, _ := strconv.Atoi(custom.Value)
|
|
||||||
if err := syscall.SetsockoptInt(int(fd), level, opt, value); err != nil {
|
|
||||||
return errors.New("failed to set CustomSockoptInt", opt, value, err)
|
|
||||||
}
|
|
||||||
} else if custom.Type == "str" {
|
|
||||||
if err := syscall.SetsockoptString(int(fd), level, opt, custom.Value); err != nil {
|
|
||||||
return errors.New("failed to set CustomSockoptString", opt, custom.Value, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return errors.New("unknown CustomSockopt type:", custom.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,3 +224,24 @@ func setReusePort(fd uintptr) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func getInterfaceIndexByName(name string) int {
|
||||||
|
ifaces, err := network.Interfaces()
|
||||||
|
if err == nil {
|
||||||
|
for _, iface := range ifaces {
|
||||||
|
if (iface.Flags&network.FlagUp == network.FlagUp) && (iface.Flags&network.FlagLoopback != network.FlagLoopback) {
|
||||||
|
addrs, _ := iface.Addrs()
|
||||||
|
for _, addr := range addrs {
|
||||||
|
if ipnet, ok := addr.(*network.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||||
|
if ipnet.IP.To4() != nil {
|
||||||
|
if iface.Name == name {
|
||||||
|
return iface.Index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
package internet
|
package internet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
@ -38,8 +35,6 @@ func bindAddr(fd uintptr, ip []byte, port uint32) error {
|
|||||||
return syscall.Bind(int(fd), sockaddr)
|
return syscall.Bind(int(fd), sockaddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyOutboundSocketOptions applies socket options for outbound connection.
|
|
||||||
// note that unlike other part of Xray, this function needs network with speified network stack(tcp4/tcp6/udp4/udp6)
|
|
||||||
func applyOutboundSocketOptions(network string, address string, fd uintptr, config *SocketConfig) error {
|
func applyOutboundSocketOptions(network string, address string, fd uintptr, config *SocketConfig) error {
|
||||||
if config.Mark != 0 {
|
if config.Mark != 0 {
|
||||||
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, int(config.Mark)); err != nil {
|
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, int(config.Mark)); err != nil {
|
||||||
@ -108,21 +103,8 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(config.CustomSockopt) > 0 {
|
if len(config.CustomSockopt) > 0 {
|
||||||
for _, custom := range config.CustomSockopt {
|
for _, custom := range config.CustomSockopt {
|
||||||
if custom.System != "" && custom.System != runtime.GOOS {
|
|
||||||
errors.LogDebug(context.Background(), "CustomSockopt system not match: ", "want ", custom.System, " got ", runtime.GOOS)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Skip unwanted network type
|
|
||||||
// network might be tcp4 or tcp6
|
|
||||||
// use HasPrefix so that "tcp" can match tcp4/6 with "tcp" if user want to control all tcp (udp is also the same)
|
|
||||||
// if it is empty, strings.HasPrefix will always return true to make it apply for all networks
|
|
||||||
if !strings.HasPrefix(network, custom.Network) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var level = 0x6 // default TCP
|
var level = 0x6 // default TCP
|
||||||
var opt int
|
var opt int
|
||||||
if len(custom.Opt) == 0 {
|
if len(custom.Opt) == 0 {
|
||||||
@ -147,6 +129,7 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if config.Tproxy.IsEnabled() {
|
if config.Tproxy.IsEnabled() {
|
||||||
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
|
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
|
||||||
@ -157,8 +140,6 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyInboundSocketOptions applies socket options for inbound listener.
|
|
||||||
// note that unlike other part of Xray, this function needs network with speified network stack(tcp4/tcp6/udp4/udp6)
|
|
||||||
func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) error {
|
func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) error {
|
||||||
if config.Mark != 0 {
|
if config.Mark != 0 {
|
||||||
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, int(config.Mark)); err != nil {
|
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, int(config.Mark)); err != nil {
|
||||||
@ -218,17 +199,6 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig)
|
|||||||
}
|
}
|
||||||
if len(config.CustomSockopt) > 0 {
|
if len(config.CustomSockopt) > 0 {
|
||||||
for _, custom := range config.CustomSockopt {
|
for _, custom := range config.CustomSockopt {
|
||||||
if custom.System != "" && custom.System != runtime.GOOS {
|
|
||||||
errors.LogDebug(context.Background(), "CustomSockopt system not match: ", "want ", custom.System, " got ", runtime.GOOS)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Skip unwanted network type
|
|
||||||
// network might be tcp4 or tcp6
|
|
||||||
// use HasPrefix so that "tcp" can match tcp4/6 with "tcp" if user want to control all tcp (udp is also the same)
|
|
||||||
// if it is empty, strings.HasPrefix will always return true to make it apply for all networks
|
|
||||||
if !strings.HasPrefix(network, custom.Network) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var level = 0x6 // default TCP
|
var level = 0x6 // default TCP
|
||||||
var opt int
|
var opt int
|
||||||
if len(custom.Opt) == 0 {
|
if len(custom.Opt) == 0 {
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
package internet
|
package internet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@ -37,11 +33,7 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("failed to find the interface").Base(err)
|
return errors.New("failed to find the interface").Base(err)
|
||||||
}
|
}
|
||||||
// easy way to check if the address is ipv4
|
isV4 := (network == "tcp4")
|
||||||
isV4 := strings.Contains(address, ".")
|
|
||||||
// note: DO NOT trust the passed network variable, it can be udp6 even if the address is ipv4
|
|
||||||
// because operating system might(always) use ipv6 socket to process ipv4
|
|
||||||
host, _, err := net.SplitHostPort(address)
|
|
||||||
if isV4 {
|
if isV4 {
|
||||||
var bytes [4]byte
|
var bytes [4]byte
|
||||||
binary.BigEndian.PutUint32(bytes[:], uint32(inf.Index))
|
binary.BigEndian.PutUint32(bytes[:], uint32(inf.Index))
|
||||||
@ -49,20 +41,10 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
|
|||||||
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IP, IP_UNICAST_IF, int(idx)); err != nil {
|
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IP, IP_UNICAST_IF, int(idx)); err != nil {
|
||||||
return errors.New("failed to set IP_UNICAST_IF").Base(err)
|
return errors.New("failed to set IP_UNICAST_IF").Base(err)
|
||||||
}
|
}
|
||||||
if ip := net.ParseIP(host); ip != nil && ip.IsMulticast() && isUDPSocket(network) {
|
|
||||||
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IP, syscall.IP_MULTICAST_IF, int(idx)); err != nil {
|
|
||||||
return errors.New("failed to set IP_MULTICAST_IF").Base(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, IPV6_UNICAST_IF, inf.Index); err != nil {
|
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, IPV6_UNICAST_IF, inf.Index); err != nil {
|
||||||
return errors.New("failed to set IPV6_UNICAST_IF").Base(err)
|
return errors.New("failed to set IPV6_UNICAST_IF").Base(err)
|
||||||
}
|
}
|
||||||
if ip := net.ParseIP(host); ip != nil && ip.IsMulticast() && isUDPSocket(network) {
|
|
||||||
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, syscall.IPV6_MULTICAST_IF, inf.Index); err != nil {
|
|
||||||
return errors.New("failed to set IPV6_MULTICAST_IF").Base(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,42 +63,6 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(config.CustomSockopt) > 0 {
|
|
||||||
for _, custom := range config.CustomSockopt {
|
|
||||||
if custom.System != "" && custom.System != runtime.GOOS {
|
|
||||||
errors.LogDebug(context.Background(), "CustomSockopt system not match: ", "want ", custom.System, " got ", runtime.GOOS)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Skip unwanted network type
|
|
||||||
// network might be tcp4 or tcp6
|
|
||||||
// use HasPrefix so that "tcp" can match tcp4/6 with "tcp" if user want to control all tcp (udp is also the same)
|
|
||||||
// if it is empty, strings.HasPrefix will always return true to make it apply for all networks
|
|
||||||
if !strings.HasPrefix(network, custom.Network) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var level = 0x6 // default TCP
|
|
||||||
var opt int
|
|
||||||
if len(custom.Opt) == 0 {
|
|
||||||
return errors.New("No opt!")
|
|
||||||
} else {
|
|
||||||
opt, _ = strconv.Atoi(custom.Opt)
|
|
||||||
}
|
|
||||||
if custom.Level != "" {
|
|
||||||
level, _ = strconv.Atoi(custom.Level)
|
|
||||||
}
|
|
||||||
if custom.Type == "int" {
|
|
||||||
value, _ := strconv.Atoi(custom.Value)
|
|
||||||
if err := syscall.SetsockoptInt(syscall.Handle(fd), level, opt, value); err != nil {
|
|
||||||
return errors.New("failed to set CustomSockoptInt", opt, value, err)
|
|
||||||
}
|
|
||||||
} else if custom.Type == "str" {
|
|
||||||
return errors.New("failed to set CustomSockoptString: Str type does not supported on windows")
|
|
||||||
} else {
|
|
||||||
return errors.New("unknown CustomSockopt type:", custom.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,48 +82,6 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.V6Only {
|
|
||||||
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 1); err != nil {
|
|
||||||
return errors.New("failed to set IPV6_V6ONLY").Base(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(config.CustomSockopt) > 0 {
|
|
||||||
for _, custom := range config.CustomSockopt {
|
|
||||||
if custom.System != "" && custom.System != runtime.GOOS {
|
|
||||||
errors.LogDebug(context.Background(), "CustomSockopt system not match: ", "want ", custom.System, " got ", runtime.GOOS)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Skip unwanted network type
|
|
||||||
// network might be tcp4 or tcp6
|
|
||||||
// use HasPrefix so that "tcp" can match tcp4/6 with "tcp" if user want to control all tcp (udp is also the same)
|
|
||||||
// if it is empty, strings.HasPrefix will always return true to make it apply for all networks
|
|
||||||
if !strings.HasPrefix(network, custom.Network) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var level = 0x6 // default TCP
|
|
||||||
var opt int
|
|
||||||
if len(custom.Opt) == 0 {
|
|
||||||
return errors.New("No opt!")
|
|
||||||
} else {
|
|
||||||
opt, _ = strconv.Atoi(custom.Opt)
|
|
||||||
}
|
|
||||||
if custom.Level != "" {
|
|
||||||
level, _ = strconv.Atoi(custom.Level)
|
|
||||||
}
|
|
||||||
if custom.Type == "int" {
|
|
||||||
value, _ := strconv.Atoi(custom.Value)
|
|
||||||
if err := syscall.SetsockoptInt(syscall.Handle(fd), level, opt, value); err != nil {
|
|
||||||
return errors.New("failed to set CustomSockoptInt", opt, value, err)
|
|
||||||
}
|
|
||||||
} else if custom.Type == "str" {
|
|
||||||
return errors.New("failed to set CustomSockoptString: Str type does not supported on windows")
|
|
||||||
} else {
|
|
||||||
return errors.New("unknown CustomSockopt type:", custom.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package splithttp
|
package splithttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/crypto"
|
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -183,5 +184,9 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c RangeConfig) rand() int32 {
|
func (c RangeConfig) rand() int32 {
|
||||||
return int32(crypto.RandBetween(int64(c.From), int64(c.To)))
|
if c.From == c.To {
|
||||||
|
return c.From
|
||||||
|
}
|
||||||
|
bigInt, _ := rand.Int(rand.Reader, big.NewInt(int64(c.To-c.From)))
|
||||||
|
return c.From + int32(bigInt.Int64())
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,16 @@ import (
|
|||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// defines the maximum time an idle TCP session can survive in the tunnel, so
|
||||||
|
// it should be consistent across HTTP versions and with other transports.
|
||||||
|
const connIdleTimeout = 300 * time.Second
|
||||||
|
|
||||||
|
// consistent with quic-go
|
||||||
|
const quicgoH3KeepAlivePeriod = 10 * time.Second
|
||||||
|
|
||||||
|
// consistent with chrome
|
||||||
|
const chromeH2KeepAlivePeriod = 45 * time.Second
|
||||||
|
|
||||||
type dialerConf struct {
|
type dialerConf struct {
|
||||||
net.Destination
|
net.Destination
|
||||||
*internet.MemoryStreamConfig
|
*internet.MemoryStreamConfig
|
||||||
@ -144,13 +154,13 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
|
|||||||
|
|
||||||
if httpVersion == "3" {
|
if httpVersion == "3" {
|
||||||
if keepAlivePeriod == 0 {
|
if keepAlivePeriod == 0 {
|
||||||
keepAlivePeriod = net.QuicgoH3KeepAlivePeriod
|
keepAlivePeriod = quicgoH3KeepAlivePeriod
|
||||||
}
|
}
|
||||||
if keepAlivePeriod < 0 {
|
if keepAlivePeriod < 0 {
|
||||||
keepAlivePeriod = 0
|
keepAlivePeriod = 0
|
||||||
}
|
}
|
||||||
quicConfig := &quic.Config{
|
quicConfig := &quic.Config{
|
||||||
MaxIdleTimeout: net.ConnIdleTimeout,
|
MaxIdleTimeout: connIdleTimeout,
|
||||||
|
|
||||||
// these two are defaults of quic-go/http3. the default of quic-go (no
|
// these two are defaults of quic-go/http3. the default of quic-go (no
|
||||||
// http3) is different, so it is hardcoded here for clarity.
|
// http3) is different, so it is hardcoded here for clarity.
|
||||||
@ -158,7 +168,7 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
|
|||||||
MaxIncomingStreams: -1,
|
MaxIncomingStreams: -1,
|
||||||
KeepAlivePeriod: keepAlivePeriod,
|
KeepAlivePeriod: keepAlivePeriod,
|
||||||
}
|
}
|
||||||
transport = &http3.Transport{
|
transport = &http3.RoundTripper{
|
||||||
QUICConfig: quicConfig,
|
QUICConfig: quicConfig,
|
||||||
TLSClientConfig: gotlsConfig,
|
TLSClientConfig: gotlsConfig,
|
||||||
Dial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
Dial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||||
@ -188,7 +198,7 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
udpConn = &internet.FakePacketConn{Conn: c}
|
udpConn = &internet.FakePacketConn{c}
|
||||||
udpAddr, err = net.ResolveUDPAddr("udp", c.RemoteAddr().String())
|
udpAddr, err = net.ResolveUDPAddr("udp", c.RemoteAddr().String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -200,7 +210,7 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
|
|||||||
}
|
}
|
||||||
} else if httpVersion == "2" {
|
} else if httpVersion == "2" {
|
||||||
if keepAlivePeriod == 0 {
|
if keepAlivePeriod == 0 {
|
||||||
keepAlivePeriod = net.ChromeH2KeepAlivePeriod
|
keepAlivePeriod = chromeH2KeepAlivePeriod
|
||||||
}
|
}
|
||||||
if keepAlivePeriod < 0 {
|
if keepAlivePeriod < 0 {
|
||||||
keepAlivePeriod = 0
|
keepAlivePeriod = 0
|
||||||
@ -209,7 +219,7 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
|
|||||||
DialTLSContext: func(ctxInner context.Context, network string, addr string, cfg *gotls.Config) (net.Conn, error) {
|
DialTLSContext: func(ctxInner context.Context, network string, addr string, cfg *gotls.Config) (net.Conn, error) {
|
||||||
return dialContext(ctxInner)
|
return dialContext(ctxInner)
|
||||||
},
|
},
|
||||||
IdleConnTimeout: net.ConnIdleTimeout,
|
IdleConnTimeout: connIdleTimeout,
|
||||||
ReadIdleTimeout: keepAlivePeriod,
|
ReadIdleTimeout: keepAlivePeriod,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -220,7 +230,7 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
|
|||||||
transport = &http.Transport{
|
transport = &http.Transport{
|
||||||
DialTLSContext: httpDialContext,
|
DialTLSContext: httpDialContext,
|
||||||
DialContext: httpDialContext,
|
DialContext: httpDialContext,
|
||||||
IdleConnTimeout: net.ConnIdleTimeout,
|
IdleConnTimeout: connIdleTimeout,
|
||||||
// chunked transfer download with KeepAlives is buggy with
|
// chunked transfer download with KeepAlives is buggy with
|
||||||
// http.Client and our custom dial context.
|
// http.Client and our custom dial context.
|
||||||
DisableKeepAlives: true,
|
DisableKeepAlives: true,
|
||||||
@ -281,11 +291,11 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
|
|||||||
mode := transportConfiguration.Mode
|
mode := transportConfiguration.Mode
|
||||||
if mode == "" || mode == "auto" {
|
if mode == "" || mode == "auto" {
|
||||||
mode = "packet-up"
|
mode = "packet-up"
|
||||||
if realityConfig != nil {
|
if httpVersion == "2" {
|
||||||
mode = "stream-one"
|
|
||||||
if transportConfiguration.DownloadSettings != nil {
|
|
||||||
mode = "stream-up"
|
mode = "stream-up"
|
||||||
}
|
}
|
||||||
|
if realityConfig != nil && transportConfiguration.DownloadSettings == nil {
|
||||||
|
mode = "stream-one"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ import (
|
|||||||
"github.com/xtls/xray-core/transport/internet/reality"
|
"github.com/xtls/xray-core/transport/internet/reality"
|
||||||
"github.com/xtls/xray-core/transport/internet/stat"
|
"github.com/xtls/xray-core/transport/internet/stat"
|
||||||
"github.com/xtls/xray-core/transport/internet/tls"
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
"golang.org/x/net/http2/h2c"
|
||||||
)
|
)
|
||||||
|
|
||||||
type requestHandler struct {
|
type requestHandler struct {
|
||||||
@ -424,15 +426,11 @@ func ListenXH(ctx context.Context, address net.Address, port net.Port, streamSet
|
|||||||
|
|
||||||
handler.localAddr = l.listener.Addr()
|
handler.localAddr = l.listener.Addr()
|
||||||
|
|
||||||
// server can handle both plaintext HTTP/1.1 and h2c
|
// h2cHandler can handle both plaintext HTTP/1.1 and h2c
|
||||||
protocols := new(http.Protocols)
|
|
||||||
protocols.SetHTTP1(true)
|
|
||||||
protocols.SetUnencryptedHTTP2(true)
|
|
||||||
l.server = http.Server{
|
l.server = http.Server{
|
||||||
Handler: handler,
|
Handler: h2c.NewHandler(handler, &http2.Server{}),
|
||||||
ReadHeaderTimeout: time.Second * 4,
|
ReadHeaderTimeout: time.Second * 4,
|
||||||
MaxHeaderBytes: 8192,
|
MaxHeaderBytes: 8192,
|
||||||
Protocols: protocols,
|
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
if err := l.server.Serve(l.listener); err != nil {
|
if err := l.server.Serve(l.listener); err != nil {
|
||||||
|
@ -3,8 +3,10 @@ package splithttp_test
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
gotls "crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
gonet "net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
@ -21,6 +23,7 @@ import (
|
|||||||
. "github.com/xtls/xray-core/transport/internet/splithttp"
|
. "github.com/xtls/xray-core/transport/internet/splithttp"
|
||||||
"github.com/xtls/xray-core/transport/internet/stat"
|
"github.com/xtls/xray-core/transport/internet/stat"
|
||||||
"github.com/xtls/xray-core/transport/internet/tls"
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_ListenXHAndDial(t *testing.T) {
|
func Test_ListenXHAndDial(t *testing.T) {
|
||||||
@ -198,11 +201,17 @@ func Test_ListenXHAndDial_H2C(t *testing.T) {
|
|||||||
common.Must(err)
|
common.Must(err)
|
||||||
defer listen.Close()
|
defer listen.Close()
|
||||||
|
|
||||||
protocols := new(http.Protocols)
|
|
||||||
protocols.SetUnencryptedHTTP2(true)
|
|
||||||
client := http.Client{
|
client := http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http2.Transport{
|
||||||
Protocols: protocols,
|
// So http2.Transport doesn't complain the URL scheme isn't 'https'
|
||||||
|
AllowHTTP: true,
|
||||||
|
// even with AllowHTTP, http2.Transport will attempt to establish
|
||||||
|
// the connection using DialTLSContext. Disable TLS with custom
|
||||||
|
// dial context.
|
||||||
|
DialTLSContext: func(ctx context.Context, network, addr string, cfg *gotls.Config) (gonet.Conn, error) {
|
||||||
|
var d gonet.Dialer
|
||||||
|
return d.DialContext(ctx, network, addr)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,29 +59,25 @@ func (d *DefaultSystemDialer) Dial(ctx context.Context, src net.Address, dest ne
|
|||||||
Port: 0,
|
Port: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var lc net.ListenConfig
|
packetConn, err := ListenSystemPacket(ctx, srcAddr, sockopt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
destAddr, err := net.ResolveUDPAddr("udp", dest.NetAddr())
|
destAddr, err := net.ResolveUDPAddr("udp", dest.NetAddr())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
lc.Control = func(network, address string, c syscall.RawConn) error {
|
|
||||||
for _, ctl := range d.controllers {
|
|
||||||
if err := ctl(network, address, c); err != nil {
|
|
||||||
errors.LogInfoInner(ctx, err, "failed to apply external controller")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c.Control(func(fd uintptr) {
|
|
||||||
if sockopt != nil {
|
if sockopt != nil {
|
||||||
if err := applyOutboundSocketOptions(network, destAddr.String(), fd, sockopt); err != nil {
|
sys, err := packetConn.(*net.UDPConn).SyscallConn()
|
||||||
errors.LogInfo(ctx, err, "failed to apply socket options")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
packetConn, err := lc.ListenPacket(ctx, srcAddr.Network(), srcAddr.String())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
sys.Control(func(fd uintptr) {
|
||||||
|
if err := applyOutboundSocketOptions("udp", dest.NetAddr(), fd, sockopt); err != nil {
|
||||||
|
errors.LogInfo(ctx, err, "failed to apply socket options")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
return &PacketConnWrapper{
|
return &PacketConnWrapper{
|
||||||
Conn: packetConn,
|
Conn: packetConn,
|
||||||
Dest: destAddr,
|
Dest: destAddr,
|
||||||
|
@ -109,12 +109,12 @@ func setupOcspTicker(entry *Certificate, callback func(isReloaded, isOcspstaplin
|
|||||||
for {
|
for {
|
||||||
var isReloaded bool
|
var isReloaded bool
|
||||||
if entry.CertificatePath != "" && entry.KeyPath != "" {
|
if entry.CertificatePath != "" && entry.KeyPath != "" {
|
||||||
newCert, err := filesystem.ReadCert(entry.CertificatePath)
|
newCert, err := filesystem.ReadFile(entry.CertificatePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(context.Background(), err, "failed to parse certificate")
|
errors.LogErrorInner(context.Background(), err, "failed to parse certificate")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
newKey, err := filesystem.ReadCert(entry.KeyPath)
|
newKey, err := filesystem.ReadFile(entry.KeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(context.Background(), err, "failed to parse key")
|
errors.LogErrorInner(context.Background(), err, "failed to parse key")
|
||||||
return
|
return
|
||||||
|
@ -207,7 +207,7 @@ type Config struct {
|
|||||||
// @Critical
|
// @Critical
|
||||||
PinnedPeerCertificateChainSha256 [][]byte `protobuf:"bytes,13,rep,name=pinned_peer_certificate_chain_sha256,json=pinnedPeerCertificateChainSha256,proto3" json:"pinned_peer_certificate_chain_sha256,omitempty"`
|
PinnedPeerCertificateChainSha256 [][]byte `protobuf:"bytes,13,rep,name=pinned_peer_certificate_chain_sha256,json=pinnedPeerCertificateChainSha256,proto3" json:"pinned_peer_certificate_chain_sha256,omitempty"`
|
||||||
// @Document Some certificate public key sha256 hashes.
|
// @Document Some certificate public key sha256 hashes.
|
||||||
// @Document After normal validation (required), if one of certs in verified chain matches one of these values, the connection will be eventually accepted.
|
// @Document After normal validation (required), if the verified cert's public key hash does not match any of these values, the connection will be aborted.
|
||||||
// @Critical
|
// @Critical
|
||||||
PinnedPeerCertificatePublicKeySha256 [][]byte `protobuf:"bytes,14,rep,name=pinned_peer_certificate_public_key_sha256,json=pinnedPeerCertificatePublicKeySha256,proto3" json:"pinned_peer_certificate_public_key_sha256,omitempty"`
|
PinnedPeerCertificatePublicKeySha256 [][]byte `protobuf:"bytes,14,rep,name=pinned_peer_certificate_public_key_sha256,json=pinnedPeerCertificatePublicKeySha256,proto3" json:"pinned_peer_certificate_public_key_sha256,omitempty"`
|
||||||
MasterKeyLog string `protobuf:"bytes,15,opt,name=master_key_log,json=masterKeyLog,proto3" json:"master_key_log,omitempty"`
|
MasterKeyLog string `protobuf:"bytes,15,opt,name=master_key_log,json=masterKeyLog,proto3" json:"master_key_log,omitempty"`
|
||||||
|
@ -76,7 +76,7 @@ message Config {
|
|||||||
repeated bytes pinned_peer_certificate_chain_sha256 = 13;
|
repeated bytes pinned_peer_certificate_chain_sha256 = 13;
|
||||||
|
|
||||||
/* @Document Some certificate public key sha256 hashes.
|
/* @Document Some certificate public key sha256 hashes.
|
||||||
@Document After normal validation (required), if one of certs in verified chain matches one of these values, the connection will be eventually accepted.
|
@Document After normal validation (required), if the verified cert's public key hash does not match any of these values, the connection will be aborted.
|
||||||
@Critical
|
@Critical
|
||||||
*/
|
*/
|
||||||
repeated bytes pinned_peer_certificate_public_key_sha256 = 14;
|
repeated bytes pinned_peer_certificate_public_key_sha256 = 14;
|
||||||
|
@ -151,14 +151,10 @@ func init() {
|
|||||||
weights := utls.DefaultWeights
|
weights := utls.DefaultWeights
|
||||||
weights.TLSVersMax_Set_VersionTLS13 = 1
|
weights.TLSVersMax_Set_VersionTLS13 = 1
|
||||||
weights.FirstKeyShare_Set_CurveP256 = 0
|
weights.FirstKeyShare_Set_CurveP256 = 0
|
||||||
randomized := utls.HelloRandomizedALPN
|
randomized := utls.HelloRandomized
|
||||||
randomized.Seed, _ = utls.NewPRNGSeed()
|
randomized.Seed, _ = utls.NewPRNGSeed()
|
||||||
randomized.Weights = &weights
|
randomized.Weights = &weights
|
||||||
randomizednoalpn := utls.HelloRandomizedNoALPN
|
|
||||||
randomizednoalpn.Seed, _ = utls.NewPRNGSeed()
|
|
||||||
randomizednoalpn.Weights = &weights
|
|
||||||
PresetFingerprints["randomized"] = &randomized
|
PresetFingerprints["randomized"] = &randomized
|
||||||
PresetFingerprints["randomizednoalpn"] = &randomizednoalpn
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFingerprint(name string) (fingerprint *utls.ClientHelloID) {
|
func GetFingerprint(name string) (fingerprint *utls.ClientHelloID) {
|
||||||
@ -189,7 +185,6 @@ var PresetFingerprints = map[string]*utls.ClientHelloID{
|
|||||||
"qq": &utls.HelloQQ_Auto,
|
"qq": &utls.HelloQQ_Auto,
|
||||||
"random": nil,
|
"random": nil,
|
||||||
"randomized": nil,
|
"randomized": nil,
|
||||||
"randomizednoalpn": nil,
|
|
||||||
"unsafe": nil,
|
"unsafe": nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,15 +193,12 @@ var ModernFingerprints = map[string]*utls.ClientHelloID{
|
|||||||
"hellofirefox_99": &utls.HelloFirefox_99,
|
"hellofirefox_99": &utls.HelloFirefox_99,
|
||||||
"hellofirefox_102": &utls.HelloFirefox_102,
|
"hellofirefox_102": &utls.HelloFirefox_102,
|
||||||
"hellofirefox_105": &utls.HelloFirefox_105,
|
"hellofirefox_105": &utls.HelloFirefox_105,
|
||||||
"hellofirefox_120": &utls.HelloFirefox_120,
|
|
||||||
"hellochrome_83": &utls.HelloChrome_83,
|
"hellochrome_83": &utls.HelloChrome_83,
|
||||||
"hellochrome_87": &utls.HelloChrome_87,
|
"hellochrome_87": &utls.HelloChrome_87,
|
||||||
"hellochrome_96": &utls.HelloChrome_96,
|
"hellochrome_96": &utls.HelloChrome_96,
|
||||||
"hellochrome_100": &utls.HelloChrome_100,
|
"hellochrome_100": &utls.HelloChrome_100,
|
||||||
"hellochrome_102": &utls.HelloChrome_102,
|
"hellochrome_102": &utls.HelloChrome_102,
|
||||||
"hellochrome_106_shuffle": &utls.HelloChrome_106_Shuffle,
|
"hellochrome_106_shuffle": &utls.HelloChrome_106_Shuffle,
|
||||||
"hellochrome_120": &utls.HelloChrome_120,
|
|
||||||
"hellochrome_131": &utls.HelloChrome_131,
|
|
||||||
"helloios_13": &utls.HelloIOS_13,
|
"helloios_13": &utls.HelloIOS_13,
|
||||||
"helloios_14": &utls.HelloIOS_14,
|
"helloios_14": &utls.HelloIOS_14,
|
||||||
"helloedge_85": &utls.HelloEdge_85,
|
"helloedge_85": &utls.HelloEdge_85,
|
||||||
@ -241,12 +233,4 @@ var OtherFingerprints = map[string]*utls.ClientHelloID{
|
|||||||
"hello360_auto": &utls.Hello360_Auto,
|
"hello360_auto": &utls.Hello360_Auto,
|
||||||
"hello360_7_5": &utls.Hello360_7_5,
|
"hello360_7_5": &utls.Hello360_7_5,
|
||||||
"helloqq_auto": &utls.HelloQQ_Auto,
|
"helloqq_auto": &utls.HelloQQ_Auto,
|
||||||
|
|
||||||
// Chrome betas'
|
|
||||||
"hellochrome_100_psk": &utls.HelloChrome_100_PSK,
|
|
||||||
"hellochrome_112_psk_shuf": &utls.HelloChrome_112_PSK_Shuf,
|
|
||||||
"hellochrome_114_padding_psk_shuf": &utls.HelloChrome_114_Padding_PSK_Shuf,
|
|
||||||
"hellochrome_115_pq": &utls.HelloChrome_115_PQ,
|
|
||||||
"hellochrome_115_pq_psk": &utls.HelloChrome_115_PQ_PSK,
|
|
||||||
"hellochrome_120_pq": &utls.HelloChrome_120_PQ,
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user