Compare commits

..
1 Commits
Author SHA1 Message Date
Fangliding 2f16233259 upd reality 2026-05-01 17:50:53 +08:00
300 changed files with 5780 additions and 8214 deletions
+3 -6
View File
@@ -32,12 +32,9 @@ RUN echo '{}' >/tmp/usr/local/etc/xray/03_routing.json
RUN echo '{}' >/tmp/usr/local/etc/xray/04_policy.json
RUN echo '{}' >/tmp/usr/local/etc/xray/05_inbounds.json
RUN echo '{}' >/tmp/usr/local/etc/xray/06_outbounds.json
RUN echo '{}' >/tmp/usr/local/etc/xray/07_stats.json
RUN echo '{}' >/tmp/usr/local/etc/xray/08_fakedns.json
RUN echo '{}' >/tmp/usr/local/etc/xray/09_metrics.json
RUN echo '{}' >/tmp/usr/local/etc/xray/10_observatory.json
RUN echo '{}' >/tmp/usr/local/etc/xray/11_geodata.json
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
RUN echo '{}' >/tmp/usr/local/etc/xray/07_transport.json
RUN echo '{}' >/tmp/usr/local/etc/xray/08_stats.json
RUN echo '{}' >/tmp/usr/local/etc/xray/09_reverse.json
# Create log files
RUN mkdir -p /tmp/var/log/xray && touch \
+3 -6
View File
@@ -32,12 +32,9 @@ RUN echo '{}' >/tmp/usr/local/etc/xray/03_routing.json
RUN echo '{}' >/tmp/usr/local/etc/xray/04_policy.json
RUN echo '{}' >/tmp/usr/local/etc/xray/05_inbounds.json
RUN echo '{}' >/tmp/usr/local/etc/xray/06_outbounds.json
RUN echo '{}' >/tmp/usr/local/etc/xray/07_stats.json
RUN echo '{}' >/tmp/usr/local/etc/xray/08_fakedns.json
RUN echo '{}' >/tmp/usr/local/etc/xray/09_metrics.json
RUN echo '{}' >/tmp/usr/local/etc/xray/10_observatory.json
RUN echo '{}' >/tmp/usr/local/etc/xray/11_geodata.json
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
RUN echo '{}' >/tmp/usr/local/etc/xray/07_transport.json
RUN echo '{}' >/tmp/usr/local/etc/xray/08_stats.json
RUN echo '{}' >/tmp/usr/local/etc/xray/09_reverse.json
# Create log files
RUN mkdir -p /tmp/var/log/xray && touch \
-2
View File
@@ -11,7 +11,6 @@ on:
jobs:
check-assets:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- name: Restore Geodat Cache
uses: actions/cache/restore@v5
@@ -76,7 +75,6 @@ jobs:
fail-fast: false
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
env:
GOOS: ${{ matrix.goos}}
GOARCH: ${{ matrix.goarch }}
-5
View File
@@ -11,7 +11,6 @@ on:
jobs:
check-assets:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- name: Restore Geodat Cache
uses: actions/cache/restore@v5
@@ -162,7 +161,6 @@ jobs:
fail-fast: false
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
@@ -210,9 +208,6 @@ jobs:
go build -o build_assets/xray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
# The line below is for without running conhost.exe version. Commented for not being used. Provided for reference.
# go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
elif [[ ${GOOS} == 'android' ]]; then
echo 'Building Xray for Android...'
go build -o build_assets/xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid= -checklinkname=0" -v ./main
else
echo 'Building Xray...'
if [[ ${GOARCH} == 'mips' || ${GOARCH} == 'mipsle' ]]; then
+1 -23
View File
@@ -1,4 +1,4 @@
name: Tests and Checkings
name: Test
on:
push:
@@ -8,7 +8,6 @@ on:
jobs:
check-assets:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- name: Restore Geodat Cache
uses: actions/cache/restore@v5
@@ -37,7 +36,6 @@ jobs:
check-proto:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- name: Checkout codebase
uses: actions/checkout@v6
@@ -52,28 +50,8 @@ jobs:
fi
done
check-format:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
permissions:
contents: read
steps:
- name: Checkout codebase
uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
cache: false
- name: Check Format
run: |
go install -v mvdan.cc/gofumpt@latest
go run ./infra/vformat/main.go -mode check -pwd ./
test:
needs: check-assets
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
permissions:
contents: read
runs-on: ${{ matrix.os }}
+3 -16
View File
@@ -73,6 +73,8 @@
- [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless)
- [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU)
- Magisk
- [NetProxy-Magisk](https://github.com/Fanju6/NetProxy-Magisk)
- [Xray4Magisk](https://github.com/Asterisk4Magisk/Xray4Magisk)
- [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk)
- Homebrew
- `brew install xray`
@@ -109,8 +111,6 @@
- [Invisible Man - Xray](https://github.com/InvisibleManVPN/InvisibleMan-XRayClient)
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
- [GenyConnect](https://github.com/genyleap/GenyConnect)
- [OneXray](https://github.com/OneXray/OneXray)
- [XrayUI-dev](https://github.com/PhoenixNil/XrayUI-dev)
- Android
- [v2rayNG](https://github.com/2dust/v2rayNG)
- [X-flutter](https://github.com/XTLS/X-flutter)
@@ -118,8 +118,7 @@
- [SimpleXray](https://github.com/lhear/SimpleXray)
- [XrayFA](https://github.com/Q7DF1/XrayFA)
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
- [OneXray](https://github.com/OneXray/OneXray)
- [AsteriskNG](https://github.com/Asterisk4Magisk/AsteriskNG)
- [NetProxy-Magisk](https://github.com/Fanju6/NetProxy-Magisk)
- iOS & macOS arm64 & tvOS
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973) | [Happ tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274)
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
@@ -144,14 +143,10 @@
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
- [v2rayN](https://github.com/2dust/v2rayN)
- [GenyConnect](https://github.com/genyleap/GenyConnect)
- [OneXray](https://github.com/OneXray/OneXray)
- HarmonyOS
- [Hey](https://github.com/popsiclelmlm/Hey)
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
- iOS & macOS arm64 & tvOS
- [Anywhere](https://github.com/NodePassProject/Anywhere)
- [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)
- [Loon](https://apps.apple.com/us/app/loon/id1373567447)
- [Egern](https://apps.apple.com/us/app/egern/id1616105820)
@@ -164,7 +159,6 @@
- [xtls-sdk](https://github.com/remnawave/xtls-sdk)
- [xtlsapi](https://github.com/hiddify/xtlsapi)
- [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite)
- [flutter_vless](https://github.com/XIIIFOX/flutter_vless)
- [Xray-core-python](https://github.com/LorenEteval/Xray-core-python)
- [xray-api](https://github.com/XVGuardian/xray-api)
- [XrayR](https://github.com/XrayR-project/XrayR)
@@ -209,13 +203,6 @@ Make sure that you are using the same Go version, and remember to set the git co
CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid=" -v ./main
```
For Android:
```bash
GOOS=android GOARCH=arm64 CGO_ENABLED=1 CC=/path/to/aarch64-linux-android24-clang go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid= -checklinkname=0" -v ./main
GOOS=android GOARCH=amd64 CGO_ENABLED=1 CC=/path/to/x86_64-linux-android24-clang go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid= -checklinkname=0" -v ./main
```
If you are compiling a 32-bit MIPS/MIPSLE target, use this command instead:
```bash
+5 -19
View File
@@ -3,7 +3,6 @@ package commander
import (
"context"
"net"
"strings"
"sync"
"github.com/xtls/xray-core/common"
@@ -11,7 +10,6 @@ import (
"github.com/xtls/xray-core/common/signal/done"
core "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/outbound"
"github.com/xtls/xray-core/transport/internet"
"google.golang.org/grpc"
)
@@ -69,32 +67,20 @@ func (c *Commander) Start() error {
}
c.Unlock()
listen := func(listener net.Listener) {
var listen = func(listener net.Listener) {
if err := c.server.Serve(listener); err != nil {
errors.LogErrorInner(context.Background(), err, "failed to start grpc server")
}
}
if len(c.listen) > 0 {
var addr net.Addr
if strings.HasPrefix(c.listen, "/") || strings.HasPrefix(c.listen, "@") {
addr = &net.UnixAddr{Name: c.listen, Net: "unix"}
} else {
tcpAddr, err := net.ResolveTCPAddr("tcp", c.listen)
if err != nil {
errors.LogErrorInner(context.Background(), err, "API server failed to parse listen address ", c.listen)
return err
}
addr = tcpAddr
}
l, err := internet.ListenSystem(context.Background(), addr, nil)
if err != nil {
if l, err := net.Listen("tcp", c.listen); err != nil {
errors.LogErrorInner(context.Background(), err, "API server failed to listen on ", c.listen)
return err
} else {
errors.LogInfo(context.Background(), "API server listening on ", l.Addr())
go listen(l)
}
errors.LogInfo(context.Background(), "API server listening on ", l.Addr())
go listen(l)
return nil
}
+3 -4
View File
@@ -139,8 +139,7 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
if lenAfter == 0 {
if c.highWatermark >= minSizeForEmptyRebuild {
errors.LogDebug(
context.Background(), c.name,
errors.LogDebug(context.Background(), c.name,
" rebuilding empty cache map to reclaim memory.",
" size_before_cleanup=", lenBefore,
" peak_size_before_rebuild=", c.highWatermark,
@@ -154,8 +153,7 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
if reductionFromPeak := c.highWatermark - lenAfter; reductionFromPeak > shrinkAbsoluteThreshold &&
float64(reductionFromPeak) > float64(c.highWatermark)*shrinkRatioThreshold {
errors.LogDebug(
context.Background(), c.name,
errors.LogDebug(context.Background(), c.name,
" shrinking cache map to reclaim memory.",
" new_size=", lenAfter,
" peak_size_before_shrink=", c.highWatermark,
@@ -167,6 +165,7 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
c.highWatermark = lenAfter
go c.migrate()
}
}
type migrationEntry struct {
+70 -4
View File
@@ -5,16 +5,18 @@ import (
"context"
go_errors "errors"
"fmt"
"os"
"runtime"
"sort"
"strings"
"sync"
"time"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/geodata"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/features/dns"
)
@@ -86,7 +88,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
return nil, errors.New("failed to create hosts").Base(err)
}
defaultTag := config.Tag
var defaultTag = config.Tag
if len(config.Tag) == 0 {
defaultTag = generateRandomTag()
}
@@ -139,7 +141,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
serveExpiredTTL = *ns.ServeExpiredTTL
}
tag := defaultTag
var tag = defaultTag
if len(ns.Tag) > 0 {
tag = ns.Tag
}
@@ -221,7 +223,7 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, er
}
if s.checkSystem {
supportIPv4, supportIPv6 := utils.CheckRoutes()
supportIPv4, supportIPv6 := checkRoutes()
option.IPv4Enable = option.IPv4Enable && supportIPv4
option.IPv6Enable = option.IPv6Enable && supportIPv6
} else {
@@ -537,3 +539,67 @@ func init() {
return New(ctx, config.(*Config))
}))
}
func probeRoutes() (ipv4 bool, ipv6 bool) {
if conn, err := net.Dial("udp4", "192.33.4.12:53"); err == nil {
ipv4 = true
conn.Close()
}
if conn, err := net.Dial("udp6", "[2001:500:2::c]:53"); err == nil {
ipv6 = true
conn.Close()
}
return
}
var routeCache struct {
sync.Once
sync.RWMutex
expire time.Time
ipv4, ipv6 bool
}
func checkRoutes() (bool, bool) {
if !isGUIPlatform {
routeCache.Once.Do(func() {
routeCache.ipv4, routeCache.ipv6 = probeRoutes()
})
return routeCache.ipv4, routeCache.ipv6
}
routeCache.RWMutex.RLock()
now := time.Now()
if routeCache.expire.After(now) {
routeCache.RWMutex.RUnlock()
return routeCache.ipv4, routeCache.ipv6
}
routeCache.RWMutex.RUnlock()
routeCache.RWMutex.Lock()
defer routeCache.RWMutex.Unlock()
now = time.Now()
if routeCache.expire.After(now) { // double-check
return routeCache.ipv4, routeCache.ipv6
}
routeCache.ipv4, routeCache.ipv6 = probeRoutes() // ~2ms
routeCache.expire = now.Add(100 * time.Millisecond) // ttl
return routeCache.ipv4, routeCache.ipv6
}
var isGUIPlatform = detectGUIPlatform()
func detectGUIPlatform() bool {
switch runtime.GOOS {
case "android", "ios", "windows", "darwin":
return true
case "linux", "freebsd", "openbsd":
if t := os.Getenv("XDG_SESSION_TYPE"); t == "wayland" || t == "x11" {
return true
}
if os.Getenv("DISPLAY") != "" || os.Getenv("WAYLAND_DISPLAY") != "" {
return true
}
}
return false
}
+8 -8
View File
@@ -148,7 +148,7 @@ func TestUDPServerSubnet(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -210,7 +210,7 @@ func TestUDPServer(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -350,7 +350,7 @@ func TestPrioritizedDomain(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -421,7 +421,7 @@ func TestUDPServerIPv6(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -490,7 +490,7 @@ func TestStaticHostDomain(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -577,7 +577,7 @@ func TestIPMatch(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -696,7 +696,7 @@ func TestLocalDomain(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -953,7 +953,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
+4 -9
View File
@@ -127,20 +127,15 @@ func genEDNS0Options(clientIP net.IP, padding int) *dnsmessage.Resource {
return opt
}
func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() uint16, reqOpts *dnsmessage.Resource) ([]*dnsRequest, error) {
name, err := dnsmessage.NewName(domain)
if err != nil {
return nil, err
}
func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() uint16, reqOpts *dnsmessage.Resource) []*dnsRequest {
qA := dnsmessage.Question{
Name: name,
Name: dnsmessage.MustNewName(domain),
Type: dnsmessage.TypeA,
Class: dnsmessage.ClassINET,
}
qAAAA := dnsmessage.Question{
Name: name,
Name: dnsmessage.MustNewName(domain),
Type: dnsmessage.TypeAAAA,
Class: dnsmessage.ClassINET,
}
@@ -180,7 +175,7 @@ func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() ui
})
}
return reqs, nil
return reqs
}
// parseResponse parses DNS answers from the returned payload
+3 -11
View File
@@ -2,7 +2,6 @@ package dns
import (
"math/rand"
"strings"
"testing"
"time"
@@ -25,8 +24,7 @@ func Test_parseResponse(t *testing.T) {
ans = new(dns.Msg)
ans.Id = 1
ans.Answer = append(
ans.Answer,
ans.Answer = append(ans.Answer,
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")),
common.Must2(dns.NewRR("google.com. IN A 8.8.8.8")),
@@ -36,8 +34,7 @@ func Test_parseResponse(t *testing.T) {
ans = new(dns.Msg)
ans.Id = 2
ans.Answer = append(
ans.Answer,
ans.Answer = append(ans.Answer,
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")),
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
@@ -134,15 +131,10 @@ func Test_buildReqMsgs(t *testing.T) {
IPv6Enable: false,
FakeEnable: false,
}, nil}, 0},
{"name too long", args{strings.Repeat("a", 256), dns_feature.IPOption{
IPv4Enable: true,
IPv6Enable: true,
FakeEnable: false,
}, nil}, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got, _ := buildReqMsgs(tt.args.domain, tt.args.option, stubID, tt.args.reqOpts); !(len(got) == tt.want) {
if got := buildReqMsgs(tt.args.domain, tt.args.option, stubID, tt.args.reqOpts); !(len(got) == tt.want) {
t.Errorf("buildReqMsgs() = %v, want %v", got, tt.want)
}
})
+12 -8
View File
@@ -17,7 +17,7 @@ import (
type Holder struct {
domainToIP cache.Lru
ipRange *net.IPNet
mu sync.Mutex
mu *sync.Mutex
config *FakeDnsPool
}
@@ -49,7 +49,9 @@ func (fkdns *Holder) Start() error {
}
func (fkdns *Holder) Close() error {
// nothing to do for now, just wait GC
fkdns.domainToIP = nil
fkdns.ipRange = nil
fkdns.mu = nil
return nil
}
@@ -68,7 +70,7 @@ func NewFakeDNSHolder() (*Holder, error) {
}
func NewFakeDNSHolderConfigOnly(conf *FakeDnsPool) (*Holder, error) {
return &Holder{config: conf}, nil
return &Holder{nil, nil, nil, conf}, nil
}
func (fkdns *Holder) initializeFromConfig() error {
@@ -90,6 +92,7 @@ func (fkdns *Holder) initialize(ipPoolCidr string, lruSize int) error {
}
fkdns.domainToIP = cache.NewLru(lruSize)
fkdns.ipRange = ipRange
fkdns.mu = new(sync.Mutex)
return nil
}
@@ -100,7 +103,7 @@ func (fkdns *Holder) GetFakeIPForDomain(domain string) []net.Address {
if v, ok := fkdns.domainToIP.Get(domain); ok {
return []net.Address{v.(net.Address)}
}
currentTimeMillis := uint64(time.Now().UnixMilli())
currentTimeMillis := uint64(time.Now().UnixNano() / 1e6)
ones, bits := fkdns.ipRange.Mask.Size()
rooms := bits - ones
if rooms < 64 {
@@ -199,11 +202,12 @@ func (h *HolderMulti) Start() error {
}
func (h *HolderMulti) Close() error {
var errs []error
for _, v := range h.holders {
errs = append(errs, v.Close())
if err := v.Close(); err != nil {
return errors.New("Cannot close all fake dns pools").Base(err)
}
}
return errors.Combine(errs...)
return nil
}
func (h *HolderMulti) createHolderGroups() error {
@@ -218,7 +222,7 @@ func (h *HolderMulti) createHolderGroups() error {
}
func NewFakeDNSHolderMulti(conf *FakeDnsPoolMulti) (*HolderMulti, error) {
holderMulti := &HolderMulti{config: conf}
holderMulti := &HolderMulti{nil, conf}
if err := holderMulti.createHolderGroups(); err != nil {
return nil, err
}
+9 -10
View File
@@ -129,16 +129,15 @@ func TestFakeDnsHolderCreateMappingAndRollOver(t *testing.T) {
}
func TestFakeDNSMulti(t *testing.T) {
fakeMulti, err := NewFakeDNSHolderMulti(
&FakeDnsPoolMulti{
Pools: []*FakeDnsPool{{
IpPool: "240.0.0.0/12",
LruSize: 256,
}, {
IpPool: "fddd:c5b4:ff5f:f4f0::/64",
LruSize: 256,
}},
},
fakeMulti, err := NewFakeDNSHolderMulti(&FakeDnsPoolMulti{
Pools: []*FakeDnsPool{{
IpPool: "240.0.0.0/12",
LruSize: 256,
}, {
IpPool: "fddd:c5b4:ff5f:f4f0::/64",
LruSize: 256,
}},
},
)
common.Must(err)
+2 -3
View File
@@ -10,7 +10,6 @@ import (
"github.com/xtls/xray-core/common/geodata"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/dns"
"github.com/xtls/xray-core/features/routing"
@@ -135,7 +134,7 @@ func NewClient(
}
}
timeoutMs := 4000 * time.Millisecond
var timeoutMs = 4000 * time.Millisecond
if ns.TimeoutMs > 0 {
timeoutMs = time.Duration(ns.TimeoutMs) * time.Millisecond
}
@@ -167,7 +166,7 @@ func (c *Client) Name() string {
// 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) {
if c.checkSystem {
supportIPv4, supportIPv6 := utils.CheckRoutes()
supportIPv4, supportIPv6 := checkRoutes()
option.IPv4Enable = option.IPv4Enable && supportIPv4
option.IPv6Enable = option.IPv6Enable && supportIPv6
} else {
+2 -20
View File
@@ -137,32 +137,14 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- er
if s.Name()+"." == "DOH//"+fqdn {
errors.LogError(ctx, s.Name(), " tries to resolve itself! Use IP or set \"hosts\" instead")
if noResponseErrCh != nil {
err := errors.New("tries to resolve itself!", s.Name())
if option.IPv4Enable {
noResponseErrCh <- err
}
if option.IPv6Enable {
noResponseErrCh <- err
}
noResponseErrCh <- errors.New("tries to resolve itself!", s.Name())
}
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
// 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, err := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, int(crypto.RandBetween(100, 300))))
if err != nil {
errors.LogErrorInner(ctx, err, "failed to build dns query for ", fqdn)
if noResponseErrCh != nil {
if option.IPv4Enable {
noResponseErrCh <- err
}
if option.IPv6Enable {
noResponseErrCh <- err
}
}
return
}
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, int(crypto.RandBetween(100, 300))))
var deadline time.Time
if d, ok := ctx.Deadline(); ok {
+1
View File
@@ -18,6 +18,7 @@ type LocalNameServer struct {
// QueryIP implements Server.
func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, option dns.IPOption) (ips []net.IP, ttl uint32, err error) {
start := time.Now()
ips, ttl, err = s.client.LookupIP(domain, option)
+1 -13
View File
@@ -78,19 +78,7 @@ func (s *QUICNameServer) getCacheController() *CacheController { return s.cacheC
func (s *QUICNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
errors.LogInfo(ctx, s.Name(), " querying: ", fqdn)
reqs, err := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
if err != nil {
errors.LogErrorInner(ctx, err, "failed to build dns query for ", fqdn)
if noResponseErrCh != nil {
if option.IPv4Enable {
noResponseErrCh <- err
}
if option.IPv6Enable {
noResponseErrCh <- err
}
}
return
}
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
var deadline time.Time
if d, ok := ctx.Deadline(); ok {
+1 -13
View File
@@ -113,19 +113,7 @@ func (s *TCPNameServer) getCacheController() *CacheController {
func (s *TCPNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
reqs, err := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
if err != nil {
errors.LogErrorInner(ctx, err, "failed to build dns query for ", fqdn)
if noResponseErrCh != nil {
if option.IPv4Enable {
noResponseErrCh <- err
}
if option.IPv6Enable {
noResponseErrCh <- err
}
}
return
}
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
var deadline time.Time
if d, ok := ctx.Deadline(); ok {
+5 -13
View File
@@ -131,6 +131,8 @@ func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_prot
newReq.msg = &newMsg
s.addPendingRequest(&newReq)
b, _ := dns.PackMessage(newReq.msg)
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
b.UDP = &copyDest
s.udpServer.Dispatch(toDnsContext(newReq.ctx, s.address.String()), *s.address, b)
return
}
@@ -161,19 +163,7 @@ func (s *ClassicNameServer) getCacheController() *CacheController {
func (s *ClassicNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
reqs, err := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
if err != nil {
errors.LogErrorInner(ctx, err, "failed to build dns query for ", fqdn)
if noResponseErrCh != nil {
if option.IPv4Enable {
noResponseErrCh <- err
}
if option.IPv6Enable {
noResponseErrCh <- err
}
}
return
}
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
for _, req := range reqs {
udpReq := &udpDnsRequest{
@@ -189,6 +179,8 @@ func (s *ClassicNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<
}
return
}
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
b.UDP = &copyDest
s.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b)
}
}
+1
View File
@@ -85,6 +85,7 @@ func (p *MetricsHandler) Type() interface{} {
}
func (p *MetricsHandler) Start() error {
// direct listen a port if listen is set
if p.listen != "" {
TCPlistener, err := net.Listen("tcp", p.listen)
+2
View File
@@ -2,6 +2,7 @@ package burst
import (
"context"
"sync"
"github.com/xtls/xray-core/app/observatory"
@@ -71,6 +72,7 @@ func (o *Observer) Start() error {
o.hp.StartScheduler(func() ([]string, error) {
hs, ok := o.ohm.(outbound.HandlerSelector)
if !ok {
return nil, errors.New("outbound.Manager is not a HandlerSelector")
}
+24 -42
View File
@@ -5,7 +5,6 @@ import (
"fmt"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/xtls/xray-core/common/dice"
@@ -25,12 +24,11 @@ type HealthPingSettings struct {
// HealthPing is the health checker for balancers
type HealthPing struct {
ctx context.Context
cancelCtx context.CancelFunc
cancelPending atomic.Pointer[context.CancelFunc]
dispatcher routing.Dispatcher
access sync.Mutex
ticker *time.Ticker
ctx context.Context
dispatcher routing.Dispatcher
access sync.Mutex
ticker *time.Ticker
tickerClose chan struct{}
Settings *HealthPingSettings
Results map[string]*HealthPingRTTS
@@ -64,10 +62,10 @@ func NewHealthPing(ctx context.Context, dispatcher routing.Dispatcher, config *H
settings.Destination = "https://connectivitycheck.gstatic.com/generate_204"
}
if settings.Interval == 0 {
settings.Interval = 1 * time.Minute
} else if settings.Interval < 10*time.Second {
settings.Interval = time.Duration(1) * time.Minute
} else if settings.Interval < 10 {
errors.LogWarning(ctx, "health check interval is too small, 10s is applied")
settings.Interval = 10 * time.Second
settings.Interval = time.Duration(10) * time.Second
}
if settings.SamplingCount <= 0 {
settings.SamplingCount = 10
@@ -75,12 +73,10 @@ func NewHealthPing(ctx context.Context, dispatcher routing.Dispatcher, config *H
if settings.Timeout <= 0 {
// results are saved after all health pings finish,
// a larger timeout could possibly makes checks run longer
settings.Timeout = 5 * time.Second
settings.Timeout = time.Duration(5) * time.Second
}
ctx, cancel := context.WithCancel(ctx)
return &HealthPing{
ctx: ctx,
cancelCtx: cancel,
dispatcher: dispatcher,
Settings: settings,
Results: nil,
@@ -94,9 +90,9 @@ func (h *HealthPing) StartScheduler(selector func() ([]string, error)) {
}
interval := h.Settings.Interval * time.Duration(h.Settings.SamplingCount)
ticker := time.NewTicker(interval)
tickerClose := make(chan struct{})
h.ticker = ticker
// init run to get a fast check result
h.tickerClose = tickerClose
go func() {
tags, err := selector()
if err != nil {
@@ -114,20 +110,13 @@ func (h *HealthPing) StartScheduler(selector func() ([]string, error)) {
errors.LogWarning(h.ctx, "error select outbounds for scheduled health check: ", err)
return
}
subCtx, cancel := context.WithCancel(h.ctx)
old := h.cancelPending.Swap(&cancel)
if old != nil {
errors.LogDebug(h.ctx, "scheduled health check not finished before next round, canceling previous one")
(*old)()
}
h.doCheck(subCtx, tags, interval, h.Settings.SamplingCount)
h.cancelPending.CompareAndSwap(&cancel, nil)
h.doCheck(tags, interval, h.Settings.SamplingCount)
h.Cleanup(tags)
}()
select {
case <-ticker.C:
continue
case <-h.ctx.Done():
case <-tickerClose:
return
}
}
@@ -141,7 +130,8 @@ func (h *HealthPing) StopScheduler() {
}
h.ticker.Stop()
h.ticker = nil
h.cancelCtx()
close(h.tickerClose)
h.tickerClose = nil
}
// Check implements the HealthChecker
@@ -150,7 +140,7 @@ func (h *HealthPing) Check(tags []string) error {
return nil
}
errors.LogInfo(h.ctx, "perform one-time health check for tags ", tags)
h.doCheck(h.ctx, tags, 0, 1)
h.doCheck(tags, 0, 1)
return nil
}
@@ -161,14 +151,13 @@ type rtt struct {
// doCheck performs the 'rounds' amount checks in given 'duration'. You should make
// sure all tags are valid for current balancer
// cancel ctx will stop all pending checks
func (h *HealthPing) doCheck(ctx context.Context, tags []string, duration time.Duration, rounds int) {
func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int) {
count := len(tags) * rounds
if count == 0 {
return
}
ch := make(chan *rtt, count)
timers := make([]*time.Timer, 0, count)
for _, tag := range tags {
handler := tag
client := newPingClient(
@@ -183,7 +172,7 @@ func (h *HealthPing) doCheck(ctx context.Context, tags []string, duration time.D
if duration > 0 {
delay = time.Duration(dice.RollInt63n(int64(duration)))
}
timers = append(timers, time.AfterFunc(delay, func() {
time.AfterFunc(delay, func() {
errors.LogDebug(h.ctx, "checking ", handler)
delay, err := client.MeasureDelay(h.Settings.HttpMethod)
if err == nil {
@@ -211,21 +200,14 @@ func (h *HealthPing) doCheck(ctx context.Context, tags []string, duration time.D
handler: handler,
value: rttFailed,
}
}))
})
}
}
for i := 0; i < count; i++ {
select {
case rtt := <-ch:
if rtt.value > 0 {
// should not put results when network is down
h.PutResult(rtt.handler, rtt.value)
}
case <-ctx.Done():
for _, timer := range timers {
timer.Stop()
}
return
rtt := <-ch
if rtt.value > 0 {
// should not put results when network is down
h.PutResult(rtt.handler, rtt.value)
}
}
}
+2 -2
View File
@@ -59,7 +59,7 @@ func (h *HealthPingRTTS) Put(d time.Duration) {
if h.rtts == nil {
h.rtts = make([]*pingRTT, h.cap)
for i := 0; i < h.cap; i++ {
h.rtts[i] = &pingRTT{value: rttUntested}
h.rtts[i] = &pingRTT{}
}
h.idx = -1
}
@@ -88,7 +88,7 @@ func (h *HealthPingRTTS) getStatistics() *HealthPingStats {
validRTTs := make([]time.Duration, 0)
for _, rtt := range h.rtts {
switch {
case rtt.value == rttUntested || time.Since(rtt.time) > h.validity:
case rtt.value == 0 || time.Since(rtt.time) > h.validity:
continue
case rtt.value == rttFailed:
stats.Fail++
+1 -1
View File
@@ -186,7 +186,7 @@ func (o *Observer) probe(outbound string) ProbeResult {
return nil
})
if err != nil {
errorMessage := "the outbound " + outbound + " is dead: GET request failed:" + err.Error() + "with outbound handler report underlying connection failed"
var errorMessage = "the outbound " + outbound + " is dead: GET request failed:" + err.Error() + "with outbound handler report underlying connection failed"
errors.LogInfoInner(o.ctx, errorCollectorForRequest.UnderlyingError(), errorMessage)
return ProbeResult{Alive: false, LastErrorReason: errorMessage}
}
+1 -1
View File
@@ -138,7 +138,7 @@ func (s *handlerServer) GetInboundUsers(ctx context.Context, request *GetInbound
if len(request.Email) > 0 {
return &GetInboundUserResponse{Users: []*protocol.User{protocol.ToProtoUser(um.GetUser(ctx, request.Email))}}, nil
}
result := make([]*protocol.User, 0, 100)
var result = make([]*protocol.User, 0, 100)
users := um.GetUsers(ctx)
for _, u := range users {
result = append(result, protocol.ToProtoUser(u))
+3 -3
View File
@@ -16,10 +16,10 @@ import (
// Manager manages all inbound handlers.
type Manager struct {
access sync.RWMutex
access sync.RWMutex
untaggedHandlers []inbound.Handler
taggedHandlers map[string]inbound.Handler
running bool
taggedHandlers map[string]inbound.Handler
running bool
}
// New returns a new Manager for inbound handlers.
+6 -4
View File
@@ -18,9 +18,9 @@ import (
"github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/features/stats"
"github.com/xtls/xray-core/proxy"
hysteria_proxy "github.com/xtls/xray-core/proxy/hysteria"
"github.com/xtls/xray-core/proxy/hysteria/account"
hyCtx "github.com/xtls/xray-core/proxy/hysteria/ctx"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/hysteria"
"github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tcp"
"github.com/xtls/xray-core/transport/internet/udp"
@@ -134,8 +134,10 @@ func (w *tcpWorker) Proxy() proxy.Inbound {
func (w *tcpWorker) Start() error {
ctx := context.Background()
if v, ok := w.proxy.(*hysteria_proxy.Server); ok {
ctx = hysteria.ContextWithValidator(ctx, v.HysteriaInboundValidator())
type HysteriaInboundValidator interface{ HysteriaInboundValidator() *account.Validator }
if v, ok := w.proxy.(HysteriaInboundValidator); ok {
ctx = hyCtx.ContextWithRequireDatagram(ctx, true)
ctx = hyCtx.ContextWithValidator(ctx, v.HysteriaInboundValidator())
}
hub, err := internet.ListenTCP(ctx, w.address, w.port, w.stream, func(conn stat.Connection) {
+2 -1
View File
@@ -348,7 +348,7 @@ func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound)
errors.LogDebug(ctx, "use inbound source ip as sendthrough: ", inbound.Source.Address.String())
}
}
// case addr.Family().IsDomain():
//case addr.Family().IsDomain():
default:
ob.Gateway = addr
@@ -396,6 +396,7 @@ func (h *Handler) ProxySettings() *serial.TypedMessage {
}
func ParseRandomIP(addr net.Address, prefix string) net.Address {
_, ipnet, _ := net.ParseCIDR(addr.IP().String() + "/" + prefix)
ones, bits := ipnet.Mask.Size()
+8 -7
View File
@@ -22,8 +22,8 @@ import (
)
func TestInterfaces(t *testing.T) {
_ = outbound.Handler(new(Handler))
_ = outbound.Manager(new(Manager))
_ = (outbound.Handler)(new(Handler))
_ = (outbound.Manager)(new(Manager))
}
const xrayKey core.XrayKey = 1
@@ -43,12 +43,12 @@ func TestOutboundWithoutStatCounter(t *testing.T) {
}
v, _ := core.New(config)
v.AddFeature(outbound.Manager(new(Manager)))
v.AddFeature((outbound.Manager)(new(Manager)))
ctx := context.WithValue(context.Background(), xrayKey, v)
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
Tag: "tag",
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
})
conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146))
_, ok := conn.(*stat.CounterConnection)
@@ -73,12 +73,12 @@ func TestOutboundWithStatCounter(t *testing.T) {
}
v, _ := core.New(config)
v.AddFeature(outbound.Manager(new(Manager)))
v.AddFeature((outbound.Manager)(new(Manager)))
ctx := context.WithValue(context.Background(), xrayKey, v)
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
Tag: "tag",
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
})
conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146))
_, ok := conn.(*stat.CounterConnection)
@@ -88,6 +88,7 @@ func TestOutboundWithStatCounter(t *testing.T) {
}
func TestTagsCache(t *testing.T) {
test_duration := 10 * time.Second
threads_num := 50
delay := 10 * time.Millisecond
@@ -117,7 +118,7 @@ func TestTagsCache(t *testing.T) {
tag := fmt.Sprintf("%s%d", tags_prefix, idx)
cfg := &core.OutboundHandlerConfig{
Tag: tag,
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
}
if h, err := NewHandler(ctx, cfg); err == nil {
if err := ohm.AddHandler(ctx, h); err == nil {
+1
View File
@@ -162,6 +162,7 @@ func (m *Manager) ListHandlers(ctx context.Context) []outbound.Handler {
// Select implements outbound.HandlerSelector.
func (m *Manager) Select(selectors []string) []string {
key := strings.Join(selectors, ",")
if cache, ok := m.tagsCache.Load(key); ok {
return cache.([]string)
+1
View File
@@ -58,6 +58,7 @@ func (s *routingServer) AddRule(ctx context.Context, request *AddRuleRequest) (*
return &AddRuleResponse{}, bo.AddRule(request.Config, request.ShouldAppend)
}
return nil, errors.New("unsupported router implementation")
}
func (s *routingServer) RemoveRule(ctx context.Context, request *RemoveRuleRequest) (*RemoveRuleResponse, error) {
+2
View File
@@ -110,6 +110,7 @@ func (r *Router) PickRoute(ctx routing.Context) (routing.Route, error) {
// AddRule implements routing.Router.
func (r *Router) AddRule(config *serial.TypedMessage, shouldAppend bool) error {
inst, err := config.GetInstance()
if err != nil {
return err
@@ -226,6 +227,7 @@ func (r *Router) RemoveRule(tag string) error {
return nil
}
return errors.New("empty tag name!")
}
// ListRule implements routing.Router
+6 -22
View File
@@ -3,7 +3,6 @@ package router
import (
"context"
"math"
"slices"
"sort"
"time"
@@ -78,7 +77,7 @@ func (s *LeastLoadStrategy) PickOutbound(candidates []string) string {
}
func (s *LeastLoadStrategy) pickOutbounds(candidates []string) []*node {
qualified := s.getNodes(candidates)
qualified := s.getNodes(candidates, time.Duration(s.settings.MaxRTT))
selects := s.selectLeastLoad(qualified)
return selects
}
@@ -139,7 +138,7 @@ func (s *LeastLoadStrategy) selectLeastLoad(nodes []*node) []*node {
return nodes[:count]
}
func (s *LeastLoadStrategy) getNodes(candidates []string) []*node {
func (s *LeastLoadStrategy) getNodes(candidates []string, maxRTT time.Duration) []*node {
if s.observer == nil {
errors.LogError(s.ctx, "observer is nil")
return make([]*node, 0)
@@ -152,10 +151,12 @@ func (s *LeastLoadStrategy) getNodes(candidates []string) []*node {
results := observeResult.(*observatory.ObservationResult)
outboundlist := outboundList(candidates)
var ret []*node
for _, v := range results.Status {
if s.shouldSelectNode(v, candidates) {
if v.Alive && (v.Delay < maxRTT.Milliseconds() || maxRTT == 0) && outboundlist.contains(v.OutboundTag) {
record := &node{
Tag: v.OutboundTag,
CountAll: 1,
@@ -171,8 +172,8 @@ func (s *LeastLoadStrategy) getNodes(candidates []string) []*node {
record.RTTDeviationCost = time.Duration(s.costs.Apply(v.OutboundTag, float64(v.HealthPing.Deviation)))
record.CountAll = int(v.HealthPing.All)
record.CountFail = int(v.HealthPing.Fail)
}
}
ret = append(ret, record)
}
}
@@ -181,23 +182,6 @@ func (s *LeastLoadStrategy) getNodes(candidates []string) []*node {
return ret
}
func (s *LeastLoadStrategy) shouldSelectNode(v *observatory.OutboundStatus, candidates []string) bool {
maxRTT := time.Duration(s.settings.MaxRTT)
if !v.Alive {
return false
}
if maxRTT != 0 && v.Delay >= maxRTT.Milliseconds() {
return false
}
if !slices.Contains(candidates, v.OutboundTag) {
return false
}
if v.HealthPing != nil && v.HealthPing.All > 0 && s.settings.Tolerance > 0 && float64(v.HealthPing.Fail)/float64(v.HealthPing.All) > float64(s.settings.Tolerance) {
return false
}
return true
}
func leastloadSort(nodes []*node) {
sort.Slice(nodes, func(i, j int) bool {
left := nodes[i]
-5
View File
@@ -85,7 +85,6 @@ func TestSelectLeastExpected(t *testing.T) {
t.Errorf("expected: %v, actual: %v", expected, len(ns))
}
}
func TestSelectLeastExpected2(t *testing.T) {
strategy := &LeastLoadStrategy{
settings: &StrategyLeastLoadConfig{
@@ -103,7 +102,6 @@ func TestSelectLeastExpected2(t *testing.T) {
t.Errorf("expected: %v, actual: %v", expected, len(ns))
}
}
func TestSelectLeastExpectedAndBaselines(t *testing.T) {
strategy := &LeastLoadStrategy{
settings: &StrategyLeastLoadConfig{
@@ -124,7 +122,6 @@ func TestSelectLeastExpectedAndBaselines(t *testing.T) {
t.Errorf("expected: %v, actual: %v", expected, len(ns))
}
}
func TestSelectLeastExpectedAndBaselines2(t *testing.T) {
strategy := &LeastLoadStrategy{
settings: &StrategyLeastLoadConfig{
@@ -145,7 +142,6 @@ func TestSelectLeastExpectedAndBaselines2(t *testing.T) {
t.Errorf("expected: %v, actual: %v", expected, len(ns))
}
}
func TestSelectLeastLoadBaselines(t *testing.T) {
strategy := &LeastLoadStrategy{
settings: &StrategyLeastLoadConfig{
@@ -164,7 +160,6 @@ func TestSelectLeastLoadBaselines(t *testing.T) {
t.Errorf("expected: %v, actual: %v", expected, len(ns))
}
}
func TestSelectLeastLoadBaselinesNoQualified(t *testing.T) {
strategy := &LeastLoadStrategy{
settings: &StrategyLeastLoadConfig{
+49 -3
View File
@@ -7,15 +7,61 @@ import (
"io"
"net"
"net/http"
"path/filepath"
"runtime"
"strings"
"sync"
"syscall"
"time"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/features/routing"
routing_session "github.com/xtls/xray-core/features/routing/session"
)
// parseURL splits a webhook URL into an HTTP URL and an optional Unix socket
// path. For regular http/https URLs the input is returned unchanged with an
// empty socketPath. For Unix sockets the format is:
//
// /path/to/socket.sock:/http/path
// @abstract:/http/path
// @@padded:/http/path
//
// The :/ separator after the socket path delimits the HTTP request path.
// If omitted, "/" is used.
func parseURL(raw string) (httpURL, socketPath string) {
if len(raw) == 0 || (!filepath.IsAbs(raw) && raw[0] != '@') {
return raw, ""
}
if idx := strings.Index(raw, ":/"); idx >= 0 {
return "http://localhost" + raw[idx+1:], raw[:idx]
}
return "http://localhost/", raw
}
// resolveSocketPath applies platform-specific transformations to a Unix
// socket path, matching the behaviour of the listen side in
// transport/internet/system_listener.go.
//
// For abstract sockets (prefix @) on Linux/Android:
// - single @ — used as-is (lock-free abstract socket)
// - double @@ — stripped to single @ and padded to
// syscall.RawSockaddrUnix{}.Path length (HAProxy compat)
func resolveSocketPath(path string) string {
if len(path) == 0 || path[0] != '@' {
return path
}
if runtime.GOOS != "linux" && runtime.GOOS != "android" {
return path
}
if len(path) > 1 && path[1] == '@' {
fullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path))
copy(fullAddr, path[1:])
return string(fullAddr)
}
return path
}
func ptr[T any](v T) *T { return &v }
type event struct {
@@ -50,7 +96,7 @@ func NewWebhookNotifier(cfg *WebhookConfig) (*WebhookNotifier, error) {
return nil, nil
}
httpURL, socketPath := utils.SplitHTTPUnixURL(cfg.Url)
httpURL, socketPath := parseURL(cfg.Url)
h := &WebhookNotifier{
url: httpURL,
deduplication: cfg.Deduplication,
@@ -61,7 +107,7 @@ func NewWebhookNotifier(cfg *WebhookConfig) (*WebhookNotifier, error) {
}
if socketPath != "" {
dialAddr := utils.ResolveSocketPath(socketPath)
dialAddr := resolveSocketPath(socketPath)
h.client.Transport = &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
var d net.Dialer
+1 -1
View File
@@ -11,7 +11,7 @@ import (
)
func TestInterface(t *testing.T) {
_ = stats.Manager(new(Manager))
_ = (stats.Manager)(new(Manager))
}
func TestStatsChannelRunnable(t *testing.T) {
+2 -3
View File
@@ -2,11 +2,10 @@ package version
import (
"context"
"strconv"
"strings"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"strconv"
"strings"
)
type Version struct {
+2 -2
View File
@@ -38,8 +38,8 @@ func MergeMulti(dest MultiBuffer, src MultiBuffer) (MultiBuffer, MultiBuffer) {
// MergeBytes merges the given bytes into MultiBuffer and return the new address of the merged MultiBuffer.
func MergeBytes(dest MultiBuffer, src []byte) MultiBuffer {
n := len(dest)
if n > 0 && !dest[n-1].IsFull() {
nBytes, _ := dest[n-1].Write(src)
if n > 0 && !(dest)[n-1].IsFull() {
nBytes, _ := (dest)[n-1].Write(src)
src = src[nBytes:]
}
+6 -6
View File
@@ -121,11 +121,11 @@ func TestPacketReader_ReadMultiBuffer(t *testing.T) {
}
func TestReaderInterface(t *testing.T) {
_ = io.Reader(new(ReadVReader))
_ = Reader(new(ReadVReader))
_ = (io.Reader)(new(ReadVReader))
_ = (Reader)(new(ReadVReader))
_ = Reader(new(BufferedReader))
_ = io.Reader(new(BufferedReader))
_ = io.ByteReader(new(BufferedReader))
_ = io.WriterTo(new(BufferedReader))
_ = (Reader)(new(BufferedReader))
_ = (io.Reader)(new(BufferedReader))
_ = (io.ByteReader)(new(BufferedReader))
_ = (io.WriterTo)(new(BufferedReader))
}
+1 -1
View File
@@ -19,7 +19,7 @@ func (r *posixReader) Init(bs []*Buffer) {
}
for idx, b := range bs {
iovecs = append(iovecs, syscall.Iovec{
Base: &b.v[0],
Base: &(b.v[0]),
})
iovecs[idx].SetLen(int(Size))
}
+8 -9
View File
@@ -8,7 +8,6 @@ import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/geodata/strmatcher"
"github.com/xtls/xray-core/common/utils"
)
type DomainMatcher interface {
@@ -26,7 +25,7 @@ type DomainMatcherFactory interface {
type MphDomainMatcherFactory struct {
sync.Mutex
shared *utils.WeakCacheMap[string, strmatcher.MphValueMatcher]
shared map[string]strmatcher.MatcherGroup // TODO: cleanup
}
func buildDomainRulesKey(rules []*DomainRule) string {
@@ -66,7 +65,7 @@ func (f *MphDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainMatch
if key != "" {
f.Lock()
defer f.Unlock()
if g, ok := f.shared.Load(key); ok {
if g := f.shared[key]; g != nil {
errors.LogDebug(context.Background(), "geodata mph domain matcher cache HIT for ", len(rules), " rules")
return g, nil
}
@@ -103,14 +102,14 @@ func (f *MphDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainMatch
return nil, err
}
if key != "" {
f.shared.Store(key, g)
f.shared[key] = g
}
return g, nil
}
type CompactDomainMatcherFactory struct {
sync.Mutex
shared *utils.WeakCacheMap[string, strmatcher.LinearAnyMatcher]
shared map[string]strmatcher.MatcherSet // TODO: cleanup
}
func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmatcher.MatcherSet, error) {
@@ -119,7 +118,7 @@ func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmat
f.Lock()
defer f.Unlock()
if s, ok := f.shared.Load(key); ok {
if s := f.shared[key]; s != nil {
errors.LogDebug(context.Background(), "geodata geosite matcher cache HIT ", key)
return s, nil
}
@@ -139,7 +138,7 @@ func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmat
}
s.Add(m)
}
f.shared.Store(key, s)
f.shared[key] = s
return s, err
}
@@ -231,8 +230,8 @@ func parseDomain(d *Domain) (strmatcher.Matcher, error) {
func newDomainMatcherFactory() DomainMatcherFactory {
switch runtime.GOOS {
case "ios", "android":
return &CompactDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.LinearAnyMatcher]()}
return &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherSet)}
default:
return &MphDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.MphValueMatcher]()}
return &MphDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}
}
}
+6 -8
View File
@@ -7,11 +7,10 @@ import (
"testing"
"github.com/xtls/xray-core/common/geodata/strmatcher"
"github.com/xtls/xray-core/common/utils"
)
func TestCompactDomainMatcher_PreservesCustomRuleIndices(t *testing.T) {
factory := &CompactDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.LinearAnyMatcher]()}
factory := &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherSet)}
matcher, err := factory.BuildMatcher([]*DomainRule{
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
@@ -32,7 +31,7 @@ func TestCompactDomainMatcher_PreservesCustomRuleIndices(t *testing.T) {
func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) {
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
factory := &CompactDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.LinearAnyMatcher]()}
factory := &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherSet)}
matcher, err := factory.BuildMatcher([]*DomainRule{
{Value: &DomainRule_Geosite{Geosite: &GeoSiteRule{File: DefaultGeoSiteDat, Code: "CN"}}},
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "163.com"}}},
@@ -51,11 +50,10 @@ func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) {
}
func TestMphDomainMatcher_MatchReturnsDetachedSlice(t *testing.T) {
matcher, err := (&MphDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.MphValueMatcher]()}).
BuildMatcher([]*DomainRule{
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
})
matcher, err := (&MphDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}).BuildMatcher([]*DomainRule{
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
})
if err != nil {
t.Fatalf("BuildMatcher() failed: %v", err)
}
+4 -5
View File
@@ -11,7 +11,6 @@ import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/utils"
"go4.org/netipx"
)
@@ -807,7 +806,7 @@ func (mm *HeuristicMultiIPMatcher) SetReverse(reverse bool) {
type IPSetFactory struct {
sync.Mutex
shared *utils.WeakCacheMap[string, IPSet]
shared map[string]*IPSet // TODO: cleanup
}
func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, error) {
@@ -816,7 +815,7 @@ func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, er
f.Lock()
defer f.Unlock()
if ipset, ok := f.shared.Load(key); ok {
if ipset := f.shared[key]; ipset != nil {
errors.LogDebug(context.Background(), "geodata geoip matcher cache HIT ", key)
return ipset, nil
}
@@ -836,7 +835,7 @@ func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, er
return nil
})
if err == nil {
f.shared.Store(key, ipset)
f.shared[key] = ipset
}
return ipset, err
}
@@ -1019,5 +1018,5 @@ func buildOptimizedIPMatcher(f *IPSetFactory, rules []*IPRule) (IPMatcher, error
}
func newIPSetFactory() *IPSetFactory {
return &IPSetFactory{shared: utils.NewWeakCacheMap[string, IPSet]()}
return &IPSetFactory{shared: make(map[string]*IPSet)}
}
+3 -3
View File
@@ -315,7 +315,7 @@ func TestIPMatcherAnyMatchAndMatches(t *testing.T) {
}
if !matcher.AnyMatch([]net.IP{
{},
net.IP{},
ip("1.1.1.1"),
ip("8.8.8.8"),
}) {
@@ -345,7 +345,7 @@ func TestIPMatcherAnyMatchAndMatches(t *testing.T) {
if matcher.Matches([]net.IP{
ip("8.8.8.8"),
{},
net.IP{},
}) {
t.Fatal("expect Matches to be false when any IP is invalid")
}
@@ -362,7 +362,7 @@ func TestIPMatcherFilterIPs(t *testing.T) {
}
matched, unmatched := matcher.FilterIPs([]net.IP{
{},
net.IP{},
ip("8.8.8.8"),
ip("91.108.255.254"),
ip("1.1.1.1"),
+1
View File
@@ -98,6 +98,7 @@ func (l *generalLogger) run() {
}
func (l *generalLogger) Handle(msg Message) {
select {
case l.buffer <- msg:
default:
-1
View File
@@ -17,7 +17,6 @@ const (
UseFreedomSplice = "xray.buf.splice"
UseVmessPadding = "xray.vmess.padding"
UseCone = "xray.cone.disabled"
UseStrictJSON = "xray.json.strict"
BufferSize = "xray.ray.buffer.size"
BrowserDialerAddress = "xray.browser.dialer"
+2 -3
View File
@@ -36,10 +36,9 @@ func TestNormalizeEnvName(t *testing.T) {
}
func TestEnvFlag(t *testing.T) {
v := EnvFlag{
if v := (EnvFlag{
Name: "xxxxx.y",
}.GetValueAsInt(10)
if v != 10 {
}.GetValueAsInt(10)); v != 10 {
t.Error("env value: ", v)
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ type ID struct {
// Equals returns true if this ID equals to the other one.
func (id *ID) Equals(another *ID) bool {
return id.uuid.Equals(&another.uuid)
return id.uuid.Equals(&(another.uuid))
}
func (id *ID) Bytes() []byte {
+1 -1
View File
@@ -229,7 +229,7 @@ func TestSniffQUICComplex(t *testing.T) {
t.Errorf("SniffQUIC() error = %v, wantErr %v", err, tt.wantErr)
return
}
if errors.Is(err, protocol.ErrProtoNeedMoreData) != tt.needsMoreData {
if (errors.Is(err, protocol.ErrProtoNeedMoreData)) != tt.needsMoreData {
t.Errorf("SniffQUIC() error = %v, expectsNoClue %v", err, tt.needsMoreData)
return
}
+2 -2
View File
@@ -5,8 +5,8 @@ import (
)
type ServerSpec struct {
Destination net.Destination
User *MemoryUser
Destination net.Destination
User *MemoryUser
}
func NewServerSpec(dest net.Destination, user *MemoryUser) *ServerSpec {
+1
View File
@@ -228,6 +228,7 @@ func isValueKind(kind reflect.Kind) bool {
}
func marshalInterface(v interface{}, ignoreNullValue bool, insertTypeInfo bool) interface{} {
if r, ok := marshalKnownType(v, ignoreNullValue, insertTypeInfo); ok {
return r
}
+3 -1
View File
@@ -27,6 +27,7 @@ func TestMashalAccount(t *testing.T) {
j, ok := MarshalToJson(user, false)
if !ok || strings.Contains(j, "_TypedMessage_") {
t.Error("marshal account failed")
}
@@ -78,12 +79,13 @@ func TestMashalStruct(t *testing.T) {
v := (*f2.Arr)[0]["foo"]["hello"]
if f1.N != f2.N || *f1.Np != *f2.Np || f1.S != f2.S || v != "world" {
if f1.N != f2.N || *(f1.Np) != *(f2.Np) || f1.S != f2.S || v != "world" {
t.Error("f1 not equal to f2")
}
}
func TestMarshalConfigJson(t *testing.T) {
buf := bytes.NewBufferString(getConfig())
config, err := iserial.DecodeJSONConfig(buf)
if err != nil {
+29
View File
@@ -4,6 +4,7 @@ import (
"testing"
. "github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
)
func TestGetInstance(t *testing.T) {
@@ -22,3 +23,31 @@ func TestConvertingNilMessage(t *testing.T) {
t.Error("expect nil, but actually not")
}
}
func TestTypedMessageRoundTripPreservesFinalmaskCustomUDPMode(t *testing.T) {
msg := &custom.UDPConfig{
Mode: "standalone",
Client: []*custom.UDPItem{
{Rand: 12, Save: "txid"},
},
}
tm := ToTypedMessage(msg)
if tm == nil {
t.Fatal("expected typed message")
}
roundTrip, err := tm.GetInstance()
if err != nil {
t.Fatalf("GetInstance() failed: %v", err)
}
udp, ok := roundTrip.(*custom.UDPConfig)
if !ok {
t.Fatalf("unexpected round-trip type: %T", roundTrip)
}
if udp.GetMode() != "standalone" {
t.Fatalf("mode lost during typed message round-trip: got %q", udp.GetMode())
}
}
+4 -5
View File
@@ -3,7 +3,6 @@ package singbridge
import (
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
)
@@ -18,14 +17,14 @@ func ToNetwork(network string) net.Network {
}
}
func ToDestination(socksaddr M.Socksaddr, network net.Network) (net.Destination, error) {
func ToDestination(socksaddr M.Socksaddr, network net.Network) net.Destination {
// IsFqdn() implicitly checks if the domain name is valid
if socksaddr.IsFqdn() {
return net.Destination{
Network: network,
Address: net.DomainAddress(socksaddr.Fqdn),
Port: net.Port(socksaddr.Port),
}, nil
}
}
// IsIP() implicitly checks if the IP address is valid
@@ -34,10 +33,10 @@ func ToDestination(socksaddr M.Socksaddr, network net.Network) (net.Destination,
Network: network,
Address: net.IPAddress(socksaddr.Addr.AsSlice()),
Port: net.Port(socksaddr.Port),
}, nil
}
}
return net.Destination{}, errors.New("invalid socks address: ", socksaddr)
return net.Destination{}
}
func ToSocksaddr(destination net.Destination) M.Socksaddr {
+2 -10
View File
@@ -26,11 +26,7 @@ func NewDialer(dialer internet.Dialer) *XrayDialer {
}
func (d *XrayDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
dest, err := ToDestination(destination, ToNetwork(network))
if err != nil {
return nil, err
}
return d.Dialer.Dial(ctx, dest)
return d.Dialer.Dial(ctx, ToDestination(destination, ToNetwork(network)))
}
func (d *XrayDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
@@ -47,17 +43,13 @@ func NewOutboundDialer(outbound proxy.Outbound, dialer internet.Dialer) *XrayOut
}
func (d *XrayOutboundDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
dest, err := ToDestination(destination, ToNetwork(network))
if err != nil {
return nil, err
}
outbounds := session.OutboundsFromContext(ctx)
if len(outbounds) == 0 {
outbounds = []*session.Outbound{{}}
ctx = session.ContextWithOutbounds(ctx, outbounds)
}
ob := outbounds[len(outbounds)-1]
ob.Target = dest
ob.Target = ToDestination(destination, ToNetwork(network))
opts := []pipe.Option{pipe.WithSizeLimit(64 * 1024)}
uplinkReader, uplinkWriter := pipe.New(opts...)
+2 -10
View File
@@ -31,23 +31,15 @@ func NewDispatcher(dispatcher routing.Dispatcher, newErrorFunc func(values ...an
}
func (d *Dispatcher) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
dest, err := ToDestination(metadata.Destination, net.Network_TCP)
if err != nil {
return err
}
xConn := NewConn(conn)
return d.upstream.DispatchLink(ctx, dest, &transport.Link{
return d.upstream.DispatchLink(ctx, ToDestination(metadata.Destination, net.Network_TCP), &transport.Link{
Reader: xConn,
Writer: xConn,
})
}
func (d *Dispatcher) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
dest, err := ToDestination(metadata.Destination, net.Network_UDP)
if err != nil {
return err
}
return d.upstream.DispatchLink(ctx, dest, &transport.Link{
return d.upstream.DispatchLink(ctx, ToDestination(metadata.Destination, net.Network_UDP), &transport.Link{
Reader: buf.NewPacketReader(conn.(io.Reader)),
Writer: buf.NewWriter(conn.(io.Writer)),
})
+30 -31
View File
@@ -10,21 +10,15 @@ import (
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/transport"
)
func CopyPacketConn(ctx context.Context, inboundConn net.Conn, link *transport.Link, destination net.Destination, serverConn net.PacketConn) error {
cancel := func() {
common.Interrupt(link.Reader)
common.Interrupt(serverConn)
}
conn := &PacketConnWrapper{
Reader: link.Reader,
Writer: link.Writer,
Dest: destination,
Conn: inboundConn,
T: signal.CancelAfterInactivity(ctx, cancel, 300*time.Second),
}
return ReturnError(bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(serverConn)))
}
@@ -35,19 +29,11 @@ type PacketConnWrapper struct {
net.Conn
Dest net.Destination
cached buf.MultiBuffer
// A simple patch to avoid goroutine leak since sing infra cannot awake read block by write err
T *signal.ActivityTimer
}
func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (addr M.Socksaddr, err error) {
w.T.Update()
defer func() {
if err != nil {
// uplinkonly
w.T.SetTimeout(2 * time.Second)
}
}()
// This ReadPacket implemented a timeout to avoid goroutine leak like PipeConnWrapper.Read()
// as a temporarily solution
func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (M.Socksaddr, error) {
if w.cached != nil {
mb, bb := buf.SplitFirst(w.cached)
if bb == nil {
@@ -65,7 +51,30 @@ func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (addr M.Socksaddr, err
return ToSocksaddr(destination), nil
}
}
mb, err := w.ReadMultiBuffer()
// timeout
type readResult struct {
mb buf.MultiBuffer
err error
}
c := make(chan readResult, 1)
go func() {
mb, err := w.ReadMultiBuffer()
c <- readResult{mb: mb, err: err}
}()
var mb buf.MultiBuffer
select {
case <-time.After(60 * time.Second):
common.Close(w.Reader)
common.Interrupt(w.Reader)
return M.Socksaddr{}, buf.ErrReadTimeout
case result := <-c:
if result.err != nil {
return M.Socksaddr{}, result.err
}
mb = result.mb
}
nb, bb := buf.SplitFirst(mb)
if bb == nil {
return M.Socksaddr{}, nil
@@ -83,22 +92,12 @@ func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (addr M.Socksaddr, err
}
}
func (w *PacketConnWrapper) WritePacket(buffer *B.Buffer, destination M.Socksaddr) (err error) {
w.T.Update()
defer func() {
if err != nil {
// downlinkonly
w.T.SetTimeout(5 * time.Second)
}
}()
endpoint, err := ToDestination(destination, net.Network_UDP)
if err != nil {
return err
}
func (w *PacketConnWrapper) WritePacket(buffer *B.Buffer, destination M.Socksaddr) error {
vBuf := buf.New()
vBuf.Write(buffer.Bytes())
endpoint := ToDestination(destination, net.Network_UDP)
vBuf.UDP = &endpoint
return w.WriteMultiBuffer(buf.MultiBuffer{vBuf})
return w.Writer.WriteMultiBuffer(buf.MultiBuffer{vBuf})
}
func (w *PacketConnWrapper) Close() error {
+18 -18
View File
@@ -9,7 +9,6 @@ import (
"github.com/sagernet/sing/common/bufio"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/transport"
)
@@ -23,11 +22,6 @@ func CopyConn(ctx context.Context, inboundConn net.Conn, link *transport.Link, s
} else {
conn.R = &buf.BufferedReader{Reader: link.Reader}
}
cancel := func() {
common.Interrupt(link.Reader)
common.Interrupt(serverConn)
}
conn.T = signal.CancelAfterInactivity(ctx, cancel, 300*time.Second)
return ReturnError(bufio.CopyConn(ctx, conn, serverConn))
}
@@ -35,27 +29,35 @@ type PipeConnWrapper struct {
R io.Reader
W buf.Writer
net.Conn
// A simple patch to avoid goroutine leak since sing infra cannot awake read block by write err
T *signal.ActivityTimer
}
func (w *PipeConnWrapper) Close() error {
return nil
}
// This Read implemented a timeout to avoid goroutine leak.
// as a temporarily solution
func (w *PipeConnWrapper) Read(b []byte) (n int, err error) {
w.T.Update()
n, err = w.R.Read(b)
if err != nil {
// uplinkonly
w.T.SetTimeout(2 * time.Second)
type readResult struct {
n int
err error
}
c := make(chan readResult, 1)
go func() {
n, err := w.R.Read(b)
c <- readResult{n: n, err: err}
}()
select {
case result := <-c:
return result.n, result.err
case <-time.After(300 * time.Second):
common.Close(w.R)
common.Interrupt(w.R)
return 0, buf.ErrReadTimeout
}
return
}
func (w *PipeConnWrapper) Write(p []byte) (n int, err error) {
w.T.Update()
n = len(p)
var mb buf.MultiBuffer
pLen := len(p)
@@ -74,8 +76,6 @@ func (w *PipeConnWrapper) Write(p []byte) (n int, err error) {
if err != nil {
n = 0
buf.ReleaseMulti(mb)
// downlinkonly
w.T.SetTimeout(5 * time.Second)
}
return
}
-43
View File
@@ -1,43 +0,0 @@
package task
import (
"runtime"
"golang.org/x/sync/errgroup"
)
// ParallelForN runs fn(0..n-1) in parallel across runtime.GOMAXPROCS(0) worker
// goroutines. Indices are partitioned into contiguous chunks so the number of
// spawned goroutines stays bounded regardless of n.
//
// fn must be safe to call concurrently from different goroutines (each call
// receives its own unique index). Output collected by writing to indexed slots
// in a pre-allocated slice is a common safe pattern.
//
// Returns the first non-nil error reported by fn; other workers may still be
// finishing briefly afterwards.
func ParallelForN(n int, fn func(i int) error) error {
if n <= 0 {
return nil
}
workers := max(runtime.GOMAXPROCS(0), 1)
workers = min(workers, n)
chunk := (n + workers - 1) / workers
var eg errgroup.Group
for w := range workers {
start := w * chunk
end := min(start+chunk, n)
if start >= end {
break
}
eg.Go(func() error {
for i := start; i < end; i++ {
if err := fn(i); err != nil {
return err
}
}
return nil
})
}
return eg.Wait()
}
-50
View File
@@ -1,50 +0,0 @@
package task_test
import (
"errors"
"sync/atomic"
"testing"
"github.com/xtls/xray-core/common"
. "github.com/xtls/xray-core/common/task"
)
func TestParallelForN_Empty(t *testing.T) {
called := false
err := ParallelForN(0, func(i int) error {
called = true
return nil
})
common.Must(err)
if called {
t.Fatal("fn should not be called when n=0")
}
}
func TestParallelForN_AllIndicesCovered(t *testing.T) {
const N = 10000
var seen [N]int32
err := ParallelForN(N, func(i int) error {
atomic.AddInt32(&seen[i], 1)
return nil
})
common.Must(err)
for i := 0; i < N; i++ {
if seen[i] != 1 {
t.Fatalf("index %d called %d times, expected 1", i, seen[i])
}
}
}
func TestParallelForN_Error(t *testing.T) {
boom := errors.New("boom")
err := ParallelForN(1000, func(i int) error {
if i == 42 {
return boom
}
return nil
})
if err != boom {
t.Fatalf("expected %v, got %v", boom, err)
}
}
+7 -14
View File
@@ -10,44 +10,37 @@ func TestByteSizes(t *testing.T) {
size := units.ByteSize(0)
assertSizeString(t, size, "0")
size++
assertSizeValue(
t,
assertSizeValue(t,
assertSizeString(t, size, "1.00B"),
size,
)
size <<= 10
assertSizeValue(
t,
assertSizeValue(t,
assertSizeString(t, size, "1.00KB"),
size,
)
size <<= 10
assertSizeValue(
t,
assertSizeValue(t,
assertSizeString(t, size, "1.00MB"),
size,
)
size <<= 10
assertSizeValue(
t,
assertSizeValue(t,
assertSizeString(t, size, "1.00GB"),
size,
)
size <<= 10
assertSizeValue(
t,
assertSizeValue(t,
assertSizeString(t, size, "1.00TB"),
size,
)
size <<= 10
assertSizeValue(
t,
assertSizeValue(t,
assertSizeString(t, size, "1.00PB"),
size,
)
size <<= 10
assertSizeValue(
t,
assertSizeValue(t,
assertSizeString(t, size, "1.00EB"),
size,
)
+43 -77
View File
@@ -4,10 +4,10 @@ import (
"hash/fnv"
"math"
"math/rand"
"net/http"
"strconv"
"strings"
"time"
"net/http"
"strings"
"github.com/klauspost/cpuid/v2"
)
@@ -18,7 +18,6 @@ func GetRandomizer() *rand.Rand {
fnvHash.Write([]byte(strconv.Itoa(cpuid.CPU.Family) + strconv.Itoa(cpuid.CPU.Model) + strconv.Itoa(cpuid.CPU.PhysicalCores) + strconv.Itoa(cpuid.CPU.LogicalCores) + strconv.Itoa(cpuid.CPU.CacheLine) + strconv.Itoa(cpuid.CPU.ThreadsPerCore)))
return rand.New(rand.NewSource(int64(fnvHash.Sum64())))
}
var globalRng *rand.Rand = GetRandomizer()
// The Chrome version generator will suffer from deviation of a normal distribution.
@@ -27,114 +26,83 @@ func ChromeVersion() int {
var startVersion int = 144
var timeStart int64 = time.Date(2026, 1, 13, 0, 0, 0, 0, time.UTC).Unix() / 86400
var timeCurrent int64 = time.Now().Unix() / 86400
var timeDiff int = int((timeCurrent - timeStart - 35)) - int(math.Floor(math.Pow(globalRng.Float64(), 2)*105))
var timeDiff int = int((timeCurrent - timeStart - 35)) - int(math.Floor(math.Pow(globalRng.Float64(), 2) * 105))
return startVersion + (timeDiff / 35) // It's 31.15 currently.
}
var safariMinorMap [25]int = [25]int{
0, 0, 0, 1, 1,
var safariMinorMap [25]int = [25]int{0, 0, 0, 1, 1,
1, 2, 2, 2, 2, 3, 3, 3, 4, 4,
4, 5, 5, 5, 5, 5, 6, 6, 6, 6,
}
4, 5, 5, 5, 5, 5, 6, 6, 6, 6}
// The following version generators use deterministic generators, but with the distribution scaled by a curve.
func CurlVersion() string {
// curl 8.0.0 was released on 20/03/2023.
var timeCurrent int64 = time.Now().Unix() / 86400
var timeStart int64 = time.Date(2023, 3, 20, 0, 0, 0, 0, time.UTC).Unix() / 86400
var timeDiff int = int((timeCurrent - timeStart - 60)) - int(math.Floor(math.Pow(globalRng.Float64(), 2)*165))
var timeDiff int = int((timeCurrent - timeStart - 60)) - int(math.Floor(math.Pow(globalRng.Float64(), 2) * 165))
var minorValue int = int(timeDiff / 57) // The release cadence is actually 56.67 days.
return "8." + strconv.Itoa(minorValue) + ".0"
}
func FirefoxVersion() int {
// Firefox 128 ESR was released on 09/07/2023.
var timeCurrent int64 = time.Now().Unix() / 86400
var timeStart int64 = time.Date(2024, 7, 29, 0, 0, 0, 0, time.UTC).Unix() / 86400
timeDiff := timeCurrent - timeStart - 25 - int64(math.Floor(math.Pow(globalRng.Float64(), 2)*50))
return int(timeDiff/30) + 128
var timeDiff = timeCurrent - timeStart - 25 - int64(math.Floor(math.Pow(globalRng.Float64(), 2) * 50))
return int(timeDiff / 30) + 128
}
func SafariVersion() string {
var anchoredTime time.Time = time.Now()
var releaseYear int = anchoredTime.Year()
var splitPoint time.Time = time.Date(releaseYear, 9, 23, 0, 0, 0, 0, time.UTC)
delayedDays := int(math.Floor(math.Pow(globalRng.Float64(), 3) * 75))
var delayedDays = int(math.Floor(math.Pow(globalRng.Float64(), 3) * 75))
splitPoint = splitPoint.AddDate(0, 0, delayedDays)
if anchoredTime.Compare(splitPoint) < 0 {
releaseYear--
if (anchoredTime.Compare(splitPoint) < 0) {
releaseYear --
splitPoint = time.Date(releaseYear, 9, 23, 0, 0, 0, 0, time.UTC)
splitPoint = splitPoint.AddDate(0, 0, delayedDays)
}
minorVersion := safariMinorMap[(anchoredTime.Unix()-splitPoint.Unix())/1296000]
return strconv.Itoa(releaseYear-1999) + "." + strconv.Itoa(minorVersion)
var minorVersion = safariMinorMap[(anchoredTime.Unix() - splitPoint.Unix()) / 1296000]
return strconv.Itoa(releaseYear - 1999) + "." + strconv.Itoa(minorVersion)
}
// The full Chromium brand GREASE implementation
var (
clientHintGreaseNA = []string{" ", "(", ":", "-", ".", "/", ")", ";", "=", "?", "_"}
clientHintVersionNA = []string{"8", "99", "24"}
clientHintShuffle3 = [][3]int{{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0}}
clientHintShuffle4 = [][4]int{
{0, 1, 2, 3},
{0, 1, 3, 2},
{0, 2, 1, 3},
{0, 2, 3, 1},
{0, 3, 1, 2},
{0, 3, 2, 1},
{1, 0, 2, 3},
{1, 0, 3, 2},
{1, 2, 0, 3},
{1, 2, 3, 0},
{1, 3, 0, 2},
{1, 3, 2, 0},
{2, 0, 1, 3},
{2, 0, 3, 1},
{2, 1, 0, 3},
{2, 1, 3, 0},
{2, 3, 0, 1},
{2, 3, 1, 0},
{3, 0, 1, 2},
{3, 0, 2, 1},
{3, 1, 0, 2},
{3, 1, 2, 0},
{3, 2, 0, 1},
{3, 2, 1, 0},
}
)
var clientHintGreaseNA = []string{" ", "(", ":", "-", ".", "/", ")", ";", "=", "?", "_"}
var clientHintVersionNA = []string{"8", "99", "24"}
var clientHintShuffle3 = [][3]int{{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0}}
var clientHintShuffle4 = [][4]int{
{0, 1, 2, 3}, {0, 1, 3, 2}, {0, 2, 1, 3}, {0, 2, 3, 1}, {0, 3, 1, 2}, {0, 3, 2, 1},
{1, 0, 2, 3}, {1, 0, 3, 2}, {1, 2, 0, 3}, {1, 2, 3, 0}, {1, 3, 0, 2}, {1, 3, 2, 0},
{2, 0, 1, 3}, {2, 0, 3, 1}, {2, 1, 0, 3}, {2, 1, 3, 0}, {2, 3, 0, 1}, {2, 3, 1, 0},
{3, 0, 1, 2}, {3, 0, 2, 1}, {3, 1, 0, 2}, {3, 1, 2, 0}, {3, 2, 0, 1}, {3, 2, 1, 0}}
func getGreasedChInvalidBrand(seed int) string {
return "\"Not" + clientHintGreaseNA[seed%len(clientHintGreaseNA)] + "A" + clientHintGreaseNA[(seed+1)%len(clientHintGreaseNA)] + "Brand\";v=\"" + clientHintVersionNA[seed%len(clientHintVersionNA)] + "\""
return "\"Not" + clientHintGreaseNA[seed % len(clientHintGreaseNA)] + "A" + clientHintGreaseNA[(seed + 1) % len(clientHintGreaseNA)] + "Brand\";v=\"" + clientHintVersionNA[seed % len(clientHintVersionNA)] + "\"";
}
func getGreasedChOrder(brandLength int, seed int) []int {
switch brandLength {
case 1:
return []int{0}
case 2:
return []int{seed % brandLength, (seed + 1) % brandLength}
case 3:
return clientHintShuffle3[seed%len(clientHintShuffle3)][:]
default:
return clientHintShuffle4[seed%len(clientHintShuffle4)][:]
case 1:
return []int{0}
case 2:
return []int{seed % brandLength, (seed + 1) % brandLength}
case 3:
return clientHintShuffle3[seed % len(clientHintShuffle3)][:]
default:
return clientHintShuffle4[seed % len(clientHintShuffle4)][:]
}
//return []int{}
}
func getUngreasedChUa(majorVersion int, forkName string) []string {
// Set the capacity to 4, the maximum allowed brand size, so Go will never allocate memory twice
baseChUa := make([]string, 0, 4)
baseChUa = append(baseChUa, getGreasedChInvalidBrand(majorVersion),
"\"Chromium\";v=\""+strconv.Itoa(majorVersion)+"\"")
"\"Chromium\";v=\"" + strconv.Itoa(majorVersion) + "\"")
switch forkName {
case "chrome":
baseChUa = append(baseChUa, "\"Google Chrome\";v=\""+strconv.Itoa(majorVersion)+"\"")
baseChUa = append(baseChUa, "\"Google Chrome\";v=\"" + strconv.Itoa(majorVersion) + "\"")
case "edge":
baseChUa = append(baseChUa, "\"Microsoft Edge\";v=\""+strconv.Itoa(majorVersion)+"\"")
baseChUa = append(baseChUa, "\"Microsoft Edge\";v=\"" + strconv.Itoa(majorVersion) + "\"")
}
return baseChUa
}
func getGreasedChUa(majorVersion int, forkName string) string {
ungreasedCh := getUngreasedChUa(majorVersion, forkName)
shuffleMap := getGreasedChOrder(len(ungreasedCh), majorVersion)
@@ -146,18 +114,16 @@ func getGreasedChUa(majorVersion int, forkName string) string {
}
// The code below provides a coherent default browser user agent string based on a CPU-seeded PRNG.
var (
CurlUA = "curl/" + CurlVersion()
AnchoredFirefoxVersion = strconv.Itoa(FirefoxVersion())
FirefoxUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:" + AnchoredFirefoxVersion + ".0) Gecko/20100101 Firefox/" + AnchoredFirefoxVersion + ".0"
SafariUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/" + SafariVersion() + " Safari/605.1.15"
// Chromium browsers.
AnchoredChromeVersion = ChromeVersion()
ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0 Safari/537.36"
ChromeUACH = getGreasedChUa(AnchoredChromeVersion, "chrome")
MSEdgeUA = ChromeUA + "Edg/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0"
MSEdgeUACH = getGreasedChUa(AnchoredChromeVersion, "edge")
)
var CurlUA = "curl/" + CurlVersion()
var AnchoredFirefoxVersion = strconv.Itoa(FirefoxVersion())
var FirefoxUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:" + AnchoredFirefoxVersion + ".0) Gecko/20100101 Firefox/" + AnchoredFirefoxVersion + ".0"
var SafariUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/" + SafariVersion() + " Safari/605.1.15"
// Chromium browsers.
var AnchoredChromeVersion = ChromeVersion()
var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0 Safari/537.36"
var ChromeUACH = getGreasedChUa(AnchoredChromeVersion, "chrome")
var MSEdgeUA = ChromeUA + "Edg/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0"
var MSEdgeUACH = getGreasedChUa(AnchoredChromeVersion, "edge")
func applyMasqueradedHeaders(header http.Header, browser string, variant string) {
// Browser-specific.
-73
View File
@@ -1,73 +0,0 @@
package utils
import (
"net"
"os"
"runtime"
"sync"
"time"
)
func probeRoutes() (ipv4 bool, ipv6 bool) {
if conn, err := net.Dial("udp4", "192.33.4.12:53"); err == nil {
ipv4 = true
conn.Close()
}
if conn, err := net.Dial("udp6", "[2001:500:2::c]:53"); err == nil {
ipv6 = true
conn.Close()
}
return
}
var routeCache struct {
sync.Once
sync.RWMutex
expire time.Time
ipv4, ipv6 bool
}
func CheckRoutes() (bool, bool) {
if !isGUIPlatform {
routeCache.Once.Do(func() {
routeCache.ipv4, routeCache.ipv6 = probeRoutes()
})
return routeCache.ipv4, routeCache.ipv6
}
routeCache.RWMutex.RLock()
now := time.Now()
if routeCache.expire.After(now) {
routeCache.RWMutex.RUnlock()
return routeCache.ipv4, routeCache.ipv6
}
routeCache.RWMutex.RUnlock()
routeCache.RWMutex.Lock()
defer routeCache.RWMutex.Unlock()
now = time.Now()
if routeCache.expire.After(now) { // double-check
return routeCache.ipv4, routeCache.ipv6
}
routeCache.ipv4, routeCache.ipv6 = probeRoutes() // ~2ms
routeCache.expire = now.Add(100 * time.Millisecond) // ttl
return routeCache.ipv4, routeCache.ipv6
}
var isGUIPlatform = detectGUIPlatform()
func detectGUIPlatform() bool {
switch runtime.GOOS {
case "android", "ios", "windows", "darwin":
return true
case "linux", "freebsd", "openbsd":
if t := os.Getenv("XDG_SESSION_TYPE"); t == "wayland" || t == "x11" {
return true
}
if os.Getenv("DISPLAY") != "" || os.Getenv("WAYLAND_DISPLAY") != "" {
return true
}
}
return false
}
+1 -1
View File
@@ -109,4 +109,4 @@ func (m *TypedSyncMap[K, V]) Swap(key K, value V) (previous V, loaded bool) {
previous = anyPrevious.(V)
}
return previous, loaded
}
}
-54
View File
@@ -1,54 +0,0 @@
package utils
import (
"path/filepath"
"runtime"
"strings"
"syscall"
)
// ResolveSocketPath applies platform-specific transformations to a Unix
// socket path, matching the listen-side behaviour in
// transport/internet/system_listener.go.
//
// For abstract sockets (prefix @) on Linux/Android:
// - single @ — used as-is (lock-free abstract socket)
// - double @@ — stripped to single @ and padded to
// syscall.RawSockaddrUnix{}.Path length (HAProxy compat)
//
// Filesystem paths and abstract sockets on other platforms are returned
// unchanged.
func ResolveSocketPath(path string) string {
if len(path) == 0 || path[0] != '@' {
return path
}
if runtime.GOOS != "linux" && runtime.GOOS != "android" {
return path
}
if len(path) > 1 && path[1] == '@' {
fullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path))
copy(fullAddr, path[1:])
return string(fullAddr)
}
return path
}
// SplitHTTPUnixURL splits a target into an HTTP URL and an optional Unix
// socket path. For regular http(s) URLs the input is returned unchanged
// with an empty socketPath. For Unix sockets the format is:
//
// /path/to/socket.sock[:/http/path]
// @abstract[:/http/path]
// @@padded[:/http/path]
//
// The :/ separator delimits the socket path from the HTTP request path.
// If omitted, "/" is used.
func SplitHTTPUnixURL(raw string) (httpURL, socketPath string) {
if len(raw) == 0 || (!filepath.IsAbs(raw) && raw[0] != '@') {
return raw, ""
}
if idx := strings.Index(raw, ":/"); idx >= 0 {
return "http://localhost" + raw[idx+1:], raw[:idx]
}
return "http://localhost/", raw
}
-45
View File
@@ -1,45 +0,0 @@
package utils
import (
"runtime"
"sync"
"weak"
)
// WeakCacheMap is a map that holds weak references to values.
// Use for shared expensive objects and automatic cleanup when no longer used.
// This object can be GC and no goroutine is used for cleanup.
type WeakCacheMap[K comparable, V any] struct {
mu sync.Mutex
m map[K]weak.Pointer[V]
}
func NewWeakCacheMap[K comparable, V any]() *WeakCacheMap[K, V] {
return &WeakCacheMap[K, V]{
m: make(map[K]weak.Pointer[V]),
}
}
func (c *WeakCacheMap[K, V]) Load(key K) (value *V, ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
weakPtr := c.m[key].Value()
if weakPtr != nil {
return weakPtr, true
}
return nil, false
}
func (c *WeakCacheMap[K, V]) Store(key K, value *V) {
c.mu.Lock()
defer c.mu.Unlock()
weakPtr := weak.Make(value)
c.m[key] = weakPtr
runtime.AddCleanup(value, func(struct{}) {
c.mu.Lock()
defer c.mu.Unlock()
if c.m[key] == weakPtr {
delete(c.m, key)
}
}, struct{}{})
}
+2 -2
View File
@@ -19,8 +19,8 @@ import (
var (
Version_x byte = 26
Version_y byte = 6
Version_z byte = 1
Version_y byte = 4
Version_z byte = 25
)
var (
+3 -3
View File
@@ -53,7 +53,7 @@ func TestXrayDial(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -105,7 +105,7 @@ func TestXrayDialUDPConn(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
@@ -174,7 +174,7 @@ func TestXrayDialUDP(t *testing.T) {
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
IpsBlocked: &freedom.IPRules{},
}),
},
},
+4 -4
View File
@@ -54,9 +54,9 @@ func TestXrayClose(t *testing.T) {
Listen: net.NewIPOrDomain(net.LocalHostIP),
}),
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
RewriteAddress: net.NewIPOrDomain(net.LocalHostIP),
RewritePort: uint32(0),
AllowedNetworks: []net.Network{net.Network_TCP},
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(0),
Networks: []net.Network{net.Network_TCP},
}),
},
},
@@ -66,7 +66,7 @@ func TestXrayClose(t *testing.T) {
Receiver: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(0),
User: &protocol.User{
User: &protocol.User{
Account: serial.ToTypedMessage(&vmess.Account{
Id: userID.String(),
}),
+1
View File
@@ -34,6 +34,7 @@ func (ctx *Context) GetSourceIPs() []net.IP {
}
return nil
}
// GetSourcePort implements routing.Context.
+11 -16
View File
@@ -3,7 +3,7 @@ module github.com/xtls/xray-core
go 1.26
require (
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716
github.com/apernet/quic-go v0.59.1-0.20260330051153-c402ee641eb6
github.com/cloudflare/circl v1.6.3
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
github.com/golang/mock v1.7.0-rc.1
@@ -12,25 +12,24 @@ require (
github.com/klauspost/cpuid/v2 v2.3.0
github.com/miekg/dns v1.1.72
github.com/pelletier/go-toml v1.9.5
github.com/pion/stun/v3 v3.1.2
github.com/pires/go-proxyproto v0.12.0
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
github.com/robfig/cron/v3 v3.0.1
github.com/robfig/cron/v3 v3.0.0
github.com/sagernet/sing v0.5.1
github.com/sagernet/sing-shadowsocks v0.2.7
github.com/stretchr/testify v1.11.1
github.com/vishvananda/netlink v1.3.1
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f
github.com/xtls/reality v0.0.0-20260501094811-4379845b089d
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.51.0
golang.org/x/crypto v0.50.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/net v0.55.0
golang.org/x/net v0.53.0
golang.org/x/sync v0.20.0
golang.org/x/sys v0.45.0
golang.org/x/sys v0.43.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
golang.zx2c4.com/wireguard/windows v1.0.1
google.golang.org/grpc v1.81.1
google.golang.org/grpc v1.80.0
google.golang.org/protobuf v1.36.11
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
h12.io/socks v1.0.3
@@ -44,18 +43,14 @@ require (
github.com/juju/ratelimit v1.0.2 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pion/dtls/v3 v3.1.2 // indirect
github.com/pion/logging v0.2.4 // indirect
github.com/pion/transport/v4 v4.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/vishvananda/netns v0.0.5 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
golang.org/x/mod v0.35.0 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/mod v0.34.0 // indirect
golang.org/x/text v0.36.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.44.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
golang.org/x/tools v0.43.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+32 -40
View File
@@ -1,7 +1,7 @@
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716 h1:J1O+xpLuJWkdYbw5JPGwBqIHs2J8tiEP7Py9lPqkN2I=
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=
github.com/apernet/quic-go v0.59.1-0.20260330051153-c402ee641eb6 h1:cbF95uMsQwCwAzH2i8+2lNO2TReoELLuqeeMfyBjFbY=
github.com/apernet/quic-go v0.59.1-0.20260330051153-c402ee641eb6/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
@@ -45,14 +45,6 @@ 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/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/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc=
github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/stun/v3 v3.1.2 h1:86IhD8wFn6IDW4b1/0QzoQS+f5PeA8OHHRn8UZW5ErY=
github.com/pion/stun/v3 v3.1.2/go.mod h1:H7gDic7nNwlUL05pbs6T1dtaBehh/KjupxfWw3ZI7cA=
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=
github.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -61,8 +53,8 @@ github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af h1:er2acxbi3N1nvEq6HXHUAR1nTWEJmQfqiGR8EVT9rfs=
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E=
github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
@@ -75,41 +67,41 @@ github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f h1:iy2JRioxmUpoJ3SzbFPyTxHZMbR/rSHP7dOOgYaq1O8=
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
github.com/xtls/reality v0.0.0-20260501094811-4379845b089d h1:ca0n8upCDojatNr25id4npJBwUsMmLgtrvLYDj4J0Hg=
github.com/xtls/reality v0.0.0-20260501094811-4379845b089d/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
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=
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.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
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-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
@@ -121,21 +113,21 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
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.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.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
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.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -147,10 +139,10 @@ golang.zx2c4.com/wireguard/windows v1.0.1 h1:eOxiDVbywPC+ZQqvdCK7x+ZwWXKbYv50TtH
golang.zx2c4.com/wireguard/windows v1.0.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+1 -2
View File
@@ -48,5 +48,4 @@ var configLoader = NewJSONConfigLoader(
"http": func() interface{} { return new(HTTPResponse) },
},
"type",
"",
)
"")
+1 -2
View File
@@ -3,7 +3,6 @@ package conf
import (
"encoding/json"
"fmt"
"math"
"strconv"
"strings"
@@ -200,7 +199,7 @@ func (v *PortRange) UnmarshalJSON(data []byte) error {
if err == nil {
v.From = uint32(from)
v.To = uint32(to)
if v.From > v.To || v.To > math.MaxUint16 {
if v.From > v.To {
return errors.New("invalid port range ", v.From, " -> ", v.To)
}
return nil
+33 -47
View File
@@ -12,9 +12,8 @@ import (
type DNSOutboundRuleConfig struct {
Action string `json:"action"`
QType *PortList `json:"qType"`
QType *PortList `json:"qtype"`
Domain *StringList `json:"domain"`
RCode uint32 `json:"rCode"`
}
func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
@@ -25,8 +24,8 @@ func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
rule.Action = dns.RuleAction_Direct
case "drop":
rule.Action = dns.RuleAction_Drop
case "return":
rule.Action = dns.RuleAction_Return
case "reject":
rule.Action = dns.RuleAction_Reject
case "hijack":
rule.Action = dns.RuleAction_Hijack
default:
@@ -35,8 +34,14 @@ func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
if c.QType != nil {
for _, r := range c.QType.Range {
for qType := r.From; qType <= r.To; qType++ {
rule.QType = append(rule.QType, int32(qType))
if r.From > r.To {
return nil, errors.New("invalid qtype range: ", r.String())
}
if r.To > 65535 {
return nil, errors.New("dns rule qtype out of range: ", r.String())
}
for qtype := r.From; qtype <= r.To; qtype++ {
rule.Qtype = append(rule.Qtype, int32(qtype))
}
}
}
@@ -49,46 +54,29 @@ func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
rule.Domain = rules
}
if c.RCode > 65535 {
return nil, errors.New("rCode out of range: ", c.RCode)
}
rule.RCode = c.RCode
return rule, nil
}
type DNSOutboundConfig struct {
RewriteNetwork Network `json:"rewriteNetwork"`
RewriteAddress *Address `json:"rewriteAddress"`
RewritePort uint16 `json:"rewritePort"`
Network Network `json:"network"`
Address *Address `json:"address"`
Port uint16 `json:"port"`
UserLevel uint32 `json:"userLevel"`
Rules []*DNSOutboundRuleConfig `json:"rules"`
NonIPQuery *string `json:"nonIPQuery"` // todo: remove legacy
BlockTypes *[]int32 `json:"blockTypes"` // todo: remove legacy
Network Network `json:"network"`
Address *Address `json:"address"`
Port uint16 `json:"port"`
UserLevel uint32 `json:"userLevel"`
Rules []*DNSOutboundRuleConfig `json:"rules"`
NonIPQuery *string `json:"nonIPQuery"` // todo: remove legacy
BlockTypes *[]int32 `json:"blockTypes"` // todo: remove legacy
}
func (c *DNSOutboundConfig) Build() (proto.Message, error) {
if len(c.Network) > 0 {
c.RewriteNetwork = c.Network
}
if c.Address != nil {
c.RewriteAddress = c.Address
}
if c.Port != 0 {
c.RewritePort = c.Port
}
config := &dns.Config{
RewriteServer: &net.Endpoint{
Network: c.RewriteNetwork.Build(),
Port: uint32(c.RewritePort),
Server: &net.Endpoint{
Network: c.Network.Build(),
Port: uint32(c.Port),
},
UserLevel: c.UserLevel,
}
if c.RewriteAddress != nil {
config.RewriteServer.Address = c.RewriteAddress.Build()
if c.Address != nil {
config.Server.Address = c.Address.Build()
}
// todo: remove legacy
@@ -96,7 +84,7 @@ func (c *DNSOutboundConfig) Build() (proto.Message, error) {
if c.Rules != nil {
return nil, errors.New("legacy nonIPQuery and blockTypes cannot be mixed with rules")
}
errors.PrintDeprecatedFeatureWarning(`"nonIPQuery" and "blockTypes"`, `"rules"`)
errors.PrintDeprecatedFeatureWarning(`"nonIPQuery" and "blockTypes" in DNS outbound`, `"rules"`)
rules, err := c.buildLegacyDNSPolicy()
if err != nil {
return nil, err
@@ -133,30 +121,28 @@ func (c *DNSOutboundConfig) buildLegacyDNSPolicy() ([]*dns.DNSRuleConfig, error)
if c.BlockTypes != nil && len(*c.BlockTypes) > 0 {
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Drop}
if mode == "reject" {
rule.Action = dns.RuleAction_Return
rule.RCode = 5
rule.Action = dns.RuleAction_Reject
}
for _, qType := range *c.BlockTypes {
if qType < 0 || qType > 65535 {
return nil, errors.New("legacy blockTypes qType out of range: ", qType)
for _, qtype := range *c.BlockTypes {
if qtype < 0 || qtype > 65535 {
return nil, errors.New("legacy blockTypes qtype out of range: ", qtype)
}
rule.QType = append(rule.QType, qType)
rule.Qtype = append(rule.Qtype, qtype)
}
rules = append(rules, rule)
}
{
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Hijack}
rule.QType = append(rule.QType, 1)
rule.QType = append(rule.QType, 28)
rule.Qtype = append(rule.Qtype, 1)
rule.Qtype = append(rule.Qtype, 28)
rules = append(rules, rule)
}
{
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Return}
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Reject}
if mode == "reject" {
rule.Action = dns.RuleAction_Return
rule.RCode = 5
rule.Action = dns.RuleAction_Reject
} else if mode == "drop" {
rule.Action = dns.RuleAction_Drop
} else if mode == "skip" {
+27 -32
View File
@@ -24,7 +24,7 @@ func TestDnsProxyConfig(t *testing.T) {
}`,
Parser: loadJSON(creator),
Output: &dns.Config{
RewriteServer: &net.Endpoint{
Server: &net.Endpoint{
Network: net.Network_TCP,
Address: net.NewIPOrDomain(net.IPAddress([]byte{8, 8, 8, 8})),
Port: 53,
@@ -35,24 +35,24 @@ func TestDnsProxyConfig(t *testing.T) {
Input: `{
"rules": [{
"action": "direct",
"qType": "1,3,23-24"
"qtype": "1,3,23-24"
}, {
"action": "drop",
"qType": 28,
"qtype": 28,
"domain": ["domain:example.com", "full:example.com"]
}]
}`,
Parser: loadJSON(creator),
Output: &dns.Config{
RewriteServer: &net.Endpoint{},
Server: &net.Endpoint{},
Rule: []*dns.DNSRuleConfig{
{
Action: dns.RuleAction_Direct,
QType: []int32{1, 3, 23, 24},
Qtype: []int32{1, 3, 23, 24},
},
{
Action: dns.RuleAction_Drop,
QType: []int32{28},
Qtype: []int32{28},
Domain: []*geodata.DomainRule{
{
Value: &geodata.DomainRule_Custom{
@@ -78,18 +78,16 @@ func TestDnsProxyConfig(t *testing.T) {
{
Input: `{
"rules": [{
"action": "return",
"rCode": 5,
"action": "reject",
"domain": "keyword:example"
}]
}`,
Parser: loadJSON(creator),
Output: &dns.Config{
RewriteServer: &net.Endpoint{},
Server: &net.Endpoint{},
Rule: []*dns.DNSRuleConfig{
{
Action: dns.RuleAction_Return,
RCode: 5,
Action: dns.RuleAction_Reject,
Domain: []*geodata.DomainRule{
{
Value: &geodata.DomainRule_Custom{
@@ -108,16 +106,16 @@ func TestDnsProxyConfig(t *testing.T) {
Input: `{
"rules": [{
"action": "drop",
"qType": 257
"qtype": 257
}]
}`,
Parser: loadJSON(creator),
Output: &dns.Config{
RewriteServer: &net.Endpoint{},
Server: &net.Endpoint{},
Rule: []*dns.DNSRuleConfig{
{
Action: dns.RuleAction_Drop,
QType: []int32{257},
Qtype: []int32{257},
},
},
},
@@ -138,15 +136,14 @@ func TestDnsProxyConfigLegacyCompatibility(t *testing.T) {
}`,
Parser: loadJSON(creator),
Output: &dns.Config{
RewriteServer: &net.Endpoint{},
Server: &net.Endpoint{},
Rule: []*dns.DNSRuleConfig{
{
Action: dns.RuleAction_Hijack,
QType: []int32{1, 28},
Qtype: []int32{1, 28},
},
{
Action: dns.RuleAction_Return,
RCode: 5,
Action: dns.RuleAction_Reject,
},
},
},
@@ -157,20 +154,18 @@ func TestDnsProxyConfigLegacyCompatibility(t *testing.T) {
}`,
Parser: loadJSON(creator),
Output: &dns.Config{
RewriteServer: &net.Endpoint{},
Server: &net.Endpoint{},
Rule: []*dns.DNSRuleConfig{
{
Action: dns.RuleAction_Return,
QType: []int32{1, 65},
RCode: 5,
Action: dns.RuleAction_Reject,
Qtype: []int32{1, 65},
},
{
Action: dns.RuleAction_Hijack,
QType: []int32{1, 28},
Qtype: []int32{1, 28},
},
{
Action: dns.RuleAction_Return,
RCode: 5,
Action: dns.RuleAction_Reject,
},
},
},
@@ -182,15 +177,15 @@ func TestDnsProxyConfigLegacyCompatibility(t *testing.T) {
}`,
Parser: loadJSON(creator),
Output: &dns.Config{
RewriteServer: &net.Endpoint{},
Server: &net.Endpoint{},
Rule: []*dns.DNSRuleConfig{
{
Action: dns.RuleAction_Drop,
QType: []int32{1},
Qtype: []int32{1},
},
{
Action: dns.RuleAction_Hijack,
QType: []int32{1, 28},
Qtype: []int32{1, 28},
},
{
Action: dns.RuleAction_Drop,
@@ -205,15 +200,15 @@ func TestDnsProxyConfigLegacyCompatibility(t *testing.T) {
}`,
Parser: loadJSON(creator),
Output: &dns.Config{
RewriteServer: &net.Endpoint{},
Server: &net.Endpoint{},
Rule: []*dns.DNSRuleConfig{
{
Action: dns.RuleAction_Drop,
QType: []int32{65, 28},
Qtype: []int32{65, 28},
},
{
Action: dns.RuleAction_Hijack,
QType: []int32{1, 28},
Qtype: []int32{1, 28},
},
{
Action: dns.RuleAction_Direct,
@@ -233,7 +228,7 @@ func TestDnsProxyConfigRejectsMixedLegacyAndNewFields(t *testing.T) {
_, err := loadJSON(creator)(`{
"rules": [{
"action": "direct",
"qType": 65
"qtype": 65
}],
"blockTypes": [65]
}`)
+5 -17
View File
@@ -8,39 +8,27 @@ import (
)
type DokodemoConfig struct {
AllowedNetwork *NetworkList `json:"allowedNetwork"`
RewriteAddress *Address `json:"rewriteAddress"`
RewritePort uint16 `json:"rewritePort"`
Network *NetworkList `json:"network"`
Address *Address `json:"address"`
Port uint16 `json:"port"`
PortMap map[string]string `json:"portMap"`
Network *NetworkList `json:"network"`
FollowRedirect bool `json:"followRedirect"`
UserLevel uint32 `json:"userLevel"`
}
func (v *DokodemoConfig) Build() (proto.Message, error) {
if v.Network != nil {
v.AllowedNetwork = v.Network
}
if v.Address != nil {
v.RewriteAddress = v.Address
}
if v.Port != 0 {
v.RewritePort = v.Port
}
config := new(dokodemo.Config)
config.AllowedNetworks = v.AllowedNetwork.Build()
if v.RewriteAddress != nil {
config.RewriteAddress = v.RewriteAddress.Build()
if v.Address != nil {
config.Address = v.Address.Build()
}
config.RewritePort = uint32(v.RewritePort)
config.Port = uint32(v.Port)
config.PortMap = v.PortMap
for _, v := range config.PortMap {
if _, _, err := net.SplitHostPort(v); err != nil {
return nil, errors.New("invalid portMap: ", v).Base(err)
}
}
config.Networks = v.Network.Build()
config.FollowRedirect = v.FollowRedirect
config.UserLevel = v.UserLevel
return config, nil
+5 -5
View File
@@ -24,15 +24,15 @@ func TestDokodemoConfig(t *testing.T) {
}`,
Parser: loadJSON(creator),
Output: &dokodemo.Config{
RewriteAddress: &net.IPOrDomain{
Address: &net.IPOrDomain{
Address: &net.IPOrDomain_Ip{
Ip: []byte{8, 8, 8, 8},
},
},
RewritePort: 53,
AllowedNetworks: []net.Network{net.Network_TCP},
FollowRedirect: true,
UserLevel: 1,
Port: 53,
Networks: []net.Network{net.Network_TCP},
FollowRedirect: true,
UserLevel: 1,
},
},
})
+15 -72
View File
@@ -1,7 +1,6 @@
package conf
import (
"context"
"encoding/base64"
"encoding/hex"
"net"
@@ -9,7 +8,7 @@ import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/geodata"
xnet "github.com/xtls/xray-core/common/net"
v2net "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/proxy/freedom"
"github.com/xtls/xray-core/transport/internet"
@@ -17,16 +16,15 @@ import (
)
type FreedomConfig struct {
TargetStrategy string `json:"targetStrategy"`
DomainStrategy string `json:"domainStrategy"`
Redirect string `json:"redirect"`
UserLevel uint32 `json:"userLevel"`
Fragment *Fragment `json:"fragment"`
Noise *Noise `json:"noise"`
Noises []*Noise `json:"noises"`
ProxyProtocol uint32 `json:"proxyProtocol"`
IPsBlocked *StringList `json:"ipsBlocked"`
FinalRules []*FreedomFinalRuleConfig `json:"finalRules"`
TargetStrategy string `json:"targetStrategy"`
DomainStrategy string `json:"domainStrategy"`
Redirect string `json:"redirect"`
UserLevel uint32 `json:"userLevel"`
Fragment *Fragment `json:"fragment"`
Noise *Noise `json:"noise"`
Noises []*Noise `json:"noises"`
ProxyProtocol uint32 `json:"proxyProtocol"`
IPsBlocked *StringList `json:"ipsBlocked"`
}
type Fragment struct {
@@ -43,21 +41,8 @@ type Noise struct {
ApplyTo string `json:"applyTo"`
}
type FreedomFinalRuleConfig struct {
Action string `json:"action"`
Network *NetworkList `json:"network"`
Port *PortList `json:"port"`
IP *StringList `json:"ip"`
BlockDelay *Int32Range `json:"blockDelay"`
}
// Build implements Buildable
func (c *FreedomConfig) Build() (proto.Message, error) {
if c.IPsBlocked != nil {
// todo: remove legacy
errors.LogWarning(context.Background(), `The feature "ipsBlocked" has been removed and migrated to "finalRules". Please update your config(s) according to release note and documentation.`)
}
config := new(freedom.Config)
targetStrategy := c.TargetStrategy
if targetStrategy == "" {
@@ -157,13 +142,12 @@ func (c *FreedomConfig) Build() (proto.Message, error) {
}
config.UserLevel = c.UserLevel
if len(c.Redirect) > 0 {
host, portStr, err := net.SplitHostPort(c.Redirect)
if err != nil {
return nil, errors.New("invalid redirect address: ", c.Redirect, ": ", err).Base(err)
}
port, err := xnet.PortFromString(portStr)
port, err := v2net.PortFromString(portStr)
if err != nil {
return nil, errors.New("invalid redirect port: ", c.Redirect, ": ", err).Base(err)
}
@@ -174,22 +158,19 @@ func (c *FreedomConfig) Build() (proto.Message, error) {
}
if len(host) > 0 {
config.DestinationOverride.Server.Address = xnet.NewIPOrDomain(xnet.ParseAddress(host))
config.DestinationOverride.Server.Address = v2net.NewIPOrDomain(v2net.ParseAddress(host))
}
}
if c.ProxyProtocol > 0 && c.ProxyProtocol <= 2 {
config.ProxyProtocol = c.ProxyProtocol
}
for _, r := range c.FinalRules {
rule, err := r.Build()
if c.IPsBlocked != nil {
rules, err := geodata.ParseIPRules(*c.IPsBlocked)
if err != nil {
return nil, err
}
config.FinalRules = append(config.FinalRules, rule)
config.IpsBlocked = &freedom.IPRules{Rules: rules}
}
return config, nil
}
@@ -248,41 +229,3 @@ func ParseNoise(noise *Noise) (*freedom.Noise, error) {
}
return NConfig, nil
}
func (c *FreedomFinalRuleConfig) Build() (*freedom.FinalRuleConfig, error) {
rule := &freedom.FinalRuleConfig{}
switch strings.ToLower(c.Action) {
case "allow":
rule.Action = freedom.RuleAction_Allow
case "block":
rule.Action = freedom.RuleAction_Block
default:
return nil, errors.New("unknown action: ", c.Action)
}
if c.Network != nil {
rule.Networks = c.Network.Build()
}
if c.Port != nil {
rule.PortList = c.Port.Build()
}
if c.IP != nil {
rules, err := geodata.ParseIPRules(*c.IP)
if err != nil {
return nil, err
}
rule.Ip = rules
}
if c.BlockDelay != nil {
rule.BlockDelay = &freedom.Range{
Min: uint64(c.BlockDelay.From),
Max: uint64(c.BlockDelay.To),
}
}
return rule, nil
}
-60
View File
@@ -3,7 +3,6 @@ package conf_test
import (
"testing"
"github.com/xtls/xray-core/common/geodata"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
. "github.com/xtls/xray-core/infra/conf"
@@ -39,64 +38,5 @@ func TestFreedomConfig(t *testing.T) {
UserLevel: 1,
},
},
{
Input: `{
"finalRules": [{
"action": "block",
"network": "tcp,udp",
"port": "53,443",
"ip": ["10.0.0.0/8", "2001:db8::/32"],
"blockDelay": "30-60"
}, {
"action": "allow",
"network": ["udp"]
}]
}`,
Parser: loadJSON(creator),
Output: &freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{
{
Action: freedom.RuleAction_Block,
Networks: []net.Network{net.Network_TCP, net.Network_UDP},
PortList: &net.PortList{
Range: []*net.PortRange{
{From: 53, To: 53},
{From: 443, To: 443},
},
},
Ip: []*geodata.IPRule{
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDRRule{
Cidr: &geodata.CIDR{
Ip: []byte{10, 0, 0, 0},
Prefix: 8,
},
},
},
},
{
Value: &geodata.IPRule_Custom{
Custom: &geodata.CIDRRule{
Cidr: &geodata.CIDR{
Ip: net.ParseAddress("2001:db8::").IP(),
Prefix: 32,
},
},
},
},
},
BlockDelay: &freedom.Range{
Min: 30,
Max: 60,
},
},
{
Action: freedom.RuleAction_Allow,
Networks: []net.Network{net.Network_UDP},
},
},
},
},
})
}
+4 -9
View File
@@ -23,7 +23,6 @@ func (v *HTTPAccount) Build() *http.Account {
}
type HTTPServerConfig struct {
Users []*HTTPAccount `json:"users"`
Accounts []*HTTPAccount `json:"accounts"`
Transparent bool `json:"allowTransparent"`
UserLevel uint32 `json:"userLevel"`
@@ -35,13 +34,9 @@ func (c *HTTPServerConfig) Build() (proto.Message, error) {
UserLevel: c.UserLevel,
}
if c.Accounts != nil {
c.Users = c.Accounts
}
// TODO: PB
if len(c.Users) > 0 {
if len(c.Accounts) > 0 {
config.Accounts = make(map[string]string)
for _, account := range c.Users {
for _, account := range c.Accounts {
config.Accounts[account.Username] = account.Password
}
}
@@ -56,8 +51,8 @@ type HTTPRemoteConfig struct {
}
type HTTPClientConfig struct {
Address *Address `json:"address"`
Port uint16 `json:"port"`
Address *Address `json:"address"`
Port uint16 `json:"port"`
Level uint32 `json:"level"`
Email string `json:"email"`
Username string `json:"user"`
+7 -18
View File
@@ -4,7 +4,6 @@ import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/proxy/hysteria"
"github.com/xtls/xray-core/proxy/hysteria/account"
"google.golang.org/protobuf/proto"
@@ -39,32 +38,22 @@ type HysteriaUserConfig struct {
type HysteriaServerConfig struct {
Version int32 `json:"version"`
Users []*HysteriaUserConfig `json:"users"`
Clients []*HysteriaUserConfig `json:"clients"`
Users []*HysteriaUserConfig `json:"clients"`
}
func (c *HysteriaServerConfig) Build() (proto.Message, error) {
config := new(hysteria.ServerConfig)
if c.Clients != nil {
c.Users = c.Clients
}
if len(c.Users) > 0 {
config.Users = make([]*protocol.User, len(c.Users))
processUser := func(idx int) error {
user := c.Users[idx]
acc := &account.Account{
if c.Users != nil {
for _, user := range c.Users {
account := &account.Account{
Auth: user.Auth,
}
config.Users[idx] = &protocol.User{
config.Users = append(config.Users, &protocol.User{
Email: user.Email,
Level: user.Level,
Account: serial.ToTypedMessage(acc),
}
return nil
}
if err := task.ParallelForN(len(c.Users), processUser); err != nil {
return nil, err
Account: serial.ToTypedMessage(account),
})
}
}
+11 -9
View File
@@ -1,9 +1,8 @@
package conf
import (
"strings"
"google.golang.org/protobuf/proto"
"strings"
"github.com/xtls/xray-core/app/observatory/burst"
"github.com/xtls/xray-core/app/router"
@@ -17,14 +16,17 @@ const (
strategyLeastLoad string = "leastload"
)
var strategyConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
strategyRandom: func() interface{} { return new(strategyEmptyConfig) },
strategyLeastPing: func() interface{} { return new(strategyEmptyConfig) },
strategyRoundRobin: func() interface{} { return new(strategyEmptyConfig) },
strategyLeastLoad: func() interface{} { return new(strategyLeastLoadConfig) },
}, "type", "settings")
var (
strategyConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
strategyRandom: func() interface{} { return new(strategyEmptyConfig) },
strategyLeastPing: func() interface{} { return new(strategyEmptyConfig) },
strategyRoundRobin: func() interface{} { return new(strategyEmptyConfig) },
strategyLeastLoad: func() interface{} { return new(strategyLeastLoadConfig) },
}, "type", "settings")
)
type strategyEmptyConfig struct{}
type strategyEmptyConfig struct {
}
func (v *strategyEmptyConfig) Build() (proto.Message, error) {
return nil, nil
+1 -15
View File
@@ -5,22 +5,12 @@ import (
"io"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform"
creflect "github.com/xtls/xray-core/common/reflect"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/infra/conf"
"github.com/xtls/xray-core/main/confloader"
)
// UseStrictJSON, when true, makes JSON config decoders skip the custom
// comment-stripping reader and parse input as strict RFC 8259 JSON.
//
// Enabled by setting the env variable xray.json.strict=true (or its normalized
// form XRAY_JSON_STRICT=true). Default false preserves backward-compatible
// behavior for human-edited configs that may contain comments or other
// JSON5/JSONC syntax.
var UseStrictJSON = platform.NewEnvFlag(platform.UseStrictJSON).GetValue(func() string { return "" }) == "true"
func MergeConfigFromFiles(files []*core.ConfigSource) (string, error) {
c, err := mergeConfigs(files)
if err != nil {
@@ -41,11 +31,7 @@ func mergeConfigs(files []*core.ConfigSource) (*conf.Config, error) {
if err != nil {
return nil, errors.New("failed to read config: ", file).Base(err)
}
decoder := ReaderDecoderByFormat[file.Format]
if file.Format == "json" && UseStrictJSON {
decoder = DecodeJSONConfigStrict
}
c, err := decoder(r)
c, err := ReaderDecoderByFormat[file.Format](r)
if err != nil {
return nil, errors.New("failed to decode config: ", file).Base(err)
}
-20
View File
@@ -42,9 +42,6 @@ func findOffset(b []byte, o int) *offset {
// DecodeJSONConfig reads from reader and decode the config into *conf.Config
// syntax error could be detected.
//
// Permissive: accepts JSON with Java/Python-style comments via json_reader.Reader.
// Used for local files and stdin where the config is human-edited.
func DecodeJSONConfig(reader io.Reader) (*conf.Config, error) {
jsonConfig := &conf.Config{}
@@ -72,23 +69,6 @@ func DecodeJSONConfig(reader io.Reader) (*conf.Config, error) {
return jsonConfig, nil
}
// DecodeJSONConfigStrict reads standard RFC 8259 JSON without comment-stripping.
// Used for remote sources (http/https/http+unix) where the payload is produced by
// automated systems and cannot contain JSON5/JSONC extensions. Avoids the
// byte-by-byte comment stripper and TeeReader, which are significant overhead on
// large configs.
func DecodeJSONConfigStrict(reader io.Reader) (*conf.Config, error) {
data, err := io.ReadAll(reader)
if err != nil {
return nil, errors.New("failed to read config file").Base(err)
}
jsonConfig := &conf.Config{}
if err := json.Unmarshal(data, jsonConfig); err != nil {
return nil, errors.New("failed to parse remote JSON config").Base(err)
}
return jsonConfig, nil
}
func LoadJSONConfig(reader io.Reader) (*core.Config, error) {
jsonConfig, err := DecodeJSONConfig(reader)
if err != nil {
+20 -40
View File
@@ -8,7 +8,6 @@ import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/proxy/shadowsocks"
"github.com/xtls/xray-core/proxy/shadowsocks_2022"
"google.golang.org/protobuf/proto"
@@ -45,18 +44,13 @@ type ShadowsocksServerConfig struct {
Password string `json:"password"`
Level byte `json:"level"`
Email string `json:"email"`
Users []*ShadowsocksUserConfig `json:"users"`
Clients []*ShadowsocksUserConfig `json:"clients"`
Users []*ShadowsocksUserConfig `json:"clients"`
NetworkList *NetworkList `json:"network"`
}
func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
errors.PrintNonRemovalDeprecatedFeatureWarning("Shadowsocks (with no Forward Secrecy, etc.)", "VLESS Encryption")
if v.Clients != nil {
v.Users = v.Clients
}
if C.Contains(shadowaead_2022.List, v.Cipher) {
return buildShadowsocks2022(v)
}
@@ -65,31 +59,23 @@ func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
config.Network = v.NetworkList.Build()
if v.Users != nil {
if len(v.Users) > 0 {
config.Users = make([]*protocol.User, len(v.Users))
processUser := func(idx int) error {
user := v.Users[idx]
account := &shadowsocks.Account{
Password: user.Password,
CipherType: cipherFromString(user.Cipher),
}
if account.Password == "" {
return errors.New("Shadowsocks password is not specified.")
}
if account.CipherType < shadowsocks.CipherType_AES_128_GCM ||
account.CipherType > shadowsocks.CipherType_XCHACHA20_POLY1305 {
return errors.New("unsupported cipher method: ", user.Cipher)
}
config.Users[idx] = &protocol.User{
Email: user.Email,
Level: uint32(user.Level),
Account: serial.ToTypedMessage(account),
}
return nil
for _, user := range v.Users {
account := &shadowsocks.Account{
Password: user.Password,
CipherType: cipherFromString(user.Cipher),
}
if err := task.ParallelForN(len(v.Users), processUser); err != nil {
return nil, err
if account.Password == "" {
return nil, errors.New("Shadowsocks password is not specified.")
}
if account.CipherType < shadowsocks.CipherType_AES_128_GCM ||
account.CipherType > shadowsocks.CipherType_XCHACHA20_POLY1305 {
return nil, errors.New("unsupported cipher method: ", user.Cipher)
}
config.Users = append(config.Users, &protocol.User{
Email: user.Email,
Level: uint32(user.Level),
Account: serial.ToTypedMessage(account),
})
}
} else {
account := &shadowsocks.Account{
@@ -135,24 +121,18 @@ func buildShadowsocks2022(v *ShadowsocksServerConfig) (proto.Message, error) {
config.Key = v.Password
config.Network = v.NetworkList.Build()
config.Users = make([]*protocol.User, len(v.Users))
processUser := func(idx int) error {
user := v.Users[idx]
for _, user := range v.Users {
if user.Cipher != "" {
return errors.New("shadowsocks 2022 (multi-user): users must have empty method")
return nil, errors.New("shadowsocks 2022 (multi-user): users must have empty method")
}
account := &shadowsocks_2022.Account{
Key: user.Password,
}
config.Users[idx] = &protocol.User{
config.Users = append(config.Users, &protocol.User{
Email: user.Email,
Level: uint32(user.Level),
Account: serial.ToTypedMessage(account),
}
return nil
}
if err := task.ParallelForN(len(v.Users), processUser); err != nil {
return nil, err
})
}
return config, nil
}
+3 -8
View File
@@ -29,7 +29,6 @@ const (
type SocksServerConfig struct {
AuthMethod string `json:"auth"`
Users []*SocksAccount `json:"users"`
Accounts []*SocksAccount `json:"accounts"`
UDP bool `json:"udp"`
Host *Address `json:"ip"`
@@ -48,13 +47,9 @@ func (v *SocksServerConfig) Build() (proto.Message, error) {
config.AuthType = socks.AuthType_NO_AUTH
}
if v.Accounts != nil {
v.Users = v.Accounts
}
// TODO: PB
if len(v.Users) > 0 {
config.Accounts = make(map[string]string, len(v.Users))
for _, account := range v.Users {
if len(v.Accounts) > 0 {
config.Accounts = make(map[string]string, len(v.Accounts))
for _, account := range v.Accounts {
config.Accounts[account.Username] = account.Password
}
}
+8 -8
View File
@@ -45,31 +45,31 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {
Value: []string{utils.ChromeUA},
},
{
Name: "Sec-CH-UA",
Name: "Sec-CH-UA",
Value: []string{utils.ChromeUACH},
},
{
Name: "Sec-CH-UA-Mobile",
Name: "Sec-CH-UA-Mobile",
Value: []string{"?0"},
},
{
Name: "Sec-CH-UA-Platform",
Name: "Sec-CH-UA-Platform",
Value: []string{"Windows"},
},
{
Name: "Sec-Fetch-Mode",
Name: "Sec-Fetch-Mode",
Value: []string{"no-cors", "cors", "same-origin"},
},
{
Name: "Sec-Fetch-Dest",
Name: "Sec-Fetch-Dest",
Value: []string{"empty"},
},
{
Name: "Sec-Fetch-Site",
Name: "Sec-Fetch-Site",
Value: []string{"none"},
},
{
Name: "Sec-Fetch-User",
Name: "Sec-Fetch-User",
Value: []string{"?1"},
},
{
@@ -96,7 +96,7 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {
}
if len(v.Path) > 0 {
config.Uri = append([]string(nil), v.Path...)
config.Uri = append([]string(nil), (v.Path)...)
}
if len(v.Headers) > 0 {
+137 -172
View File
@@ -6,7 +6,6 @@ import (
"encoding/hex"
"encoding/json"
"math"
"net/netip"
"net/url"
"os"
"regexp"
@@ -14,6 +13,7 @@ import (
"strconv"
"strings"
"syscall"
"time"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
@@ -23,11 +23,15 @@ import (
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/finalmask/fragment"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
"github.com/xtls/xray-core/transport/internet/finalmask/header/dns"
"github.com/xtls/xray-core/transport/internet/finalmask/header/dtls"
"github.com/xtls/xray-core/transport/internet/finalmask/header/srtp"
"github.com/xtls/xray-core/transport/internet/finalmask/header/utp"
"github.com/xtls/xray-core/transport/internet/finalmask/header/wechat"
"github.com/xtls/xray-core/transport/internet/finalmask/header/wireguard"
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm"
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/header"
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/original"
"github.com/xtls/xray-core/transport/internet/finalmask/noise"
"github.com/xtls/xray-core/transport/internet/finalmask/realm"
"github.com/xtls/xray-core/transport/internet/finalmask/salamander"
finalsudoku "github.com/xtls/xray-core/transport/internet/finalmask/sudoku"
"github.com/xtls/xray-core/transport/internet/finalmask/xdns"
@@ -44,10 +48,12 @@ import (
"google.golang.org/protobuf/proto"
)
var tcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{
"none": func() interface{} { return new(NoOpConnectionAuthenticator) },
"http": func() interface{} { return new(Authenticator) },
}, "type", "")
var (
tcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{
"none": func() interface{} { return new(NoOpConnectionAuthenticator) },
"http": func() interface{} { return new(Authenticator) },
}, "type", "")
)
type KCPConfig struct {
Mtu *uint32 `json:"mtu"`
@@ -491,8 +497,8 @@ func (b Bandwidth) Bps() (uint64, error) {
}
type UdpHop struct {
PortList PortList `json:"ports"`
Interval Int32Range `json:"interval"`
PortList json.RawMessage `json:"ports"`
Interval *Int32Range `json:"interval"`
}
type Masquerade struct {
@@ -650,8 +656,10 @@ type TLSConfig struct {
MasterKeyLog string `json:"masterKeyLog"`
PinnedPeerCertSha256 string `json:"pinnedPeerCertSha256"`
VerifyPeerCertByName string `json:"verifyPeerCertByName"`
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
ECHServerKeys string `json:"echServerKeys"`
ECHConfigList string `json:"echConfigList"`
ECHForceQuery string `json:"echForceQuery"`
ECHSocketSettings *SocketConfig `json:"echSockopt"`
}
@@ -696,7 +704,12 @@ func (c *TLSConfig) Build() (proto.Message, error) {
config.MasterKeyLog = c.MasterKeyLog
if c.AllowInsecure {
return nil, errors.PrintRemovedFeatureError(`"allowInsecure"`, `"pinnedPeerCertSha256"(pcs) and "verifyPeerCertByName"(vcn)`)
if time.Now().After(time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC)) {
return nil, errors.PrintRemovedFeatureError(`"allowInsecure"`, `"pinnedPeerCertSha256"`)
} else {
errors.LogWarning(context.Background(), `"allowInsecure" will be removed automatically after 2026-06-01, please use "pinnedPeerCertSha256"(pcs) and "verifyPeerCertByName"(vcn) instead, PLEASE CONTACT YOUR SERVICE PROVIDER (AIRPORT)`)
config.AllowInsecure = true
}
}
if c.PinnedPeerCertSha256 != "" {
for v := range strings.SplitSeq(c.PinnedPeerCertSha256, ",") {
@@ -715,6 +728,10 @@ func (c *TLSConfig) Build() (proto.Message, error) {
config.PinnedPeerCertSha256 = append(config.PinnedPeerCertSha256, hashValue)
}
}
if c.VerifyPeerCertInNames != nil {
return nil, errors.PrintRemovedFeatureError(`"verifyPeerCertInNames"`, `"verifyPeerCertByName"`)
}
if c.VerifyPeerCertByName != "" {
for v := range strings.SplitSeq(c.VerifyPeerCertByName, ",") {
v = strings.TrimSpace(v)
@@ -732,6 +749,13 @@ func (c *TLSConfig) Build() (proto.Message, error) {
}
config.EchServerKeys = EchPrivateKey
}
switch c.ECHForceQuery {
case "none", "half", "full", "":
config.EchForceQuery = c.ECHForceQuery
default:
return nil, errors.New(`invalid "echForceQuery": `, c.ECHForceQuery)
}
config.EchForceQuery = c.ECHForceQuery
config.EchConfigList = c.ECHConfigList
if c.ECHSocketSettings != nil {
ss, err := c.ECHSocketSettings.Build()
@@ -1013,7 +1037,7 @@ type HappyEyeballsConfig struct {
}
func (h *HappyEyeballsConfig) UnmarshalJSON(data []byte) error {
innerHappyEyeballsConfig := struct {
var innerHappyEyeballsConfig = struct {
PrioritizeIPv6 bool `json:"prioritizeIPv6"`
TryDelayMs uint64 `json:"tryDelayMs"`
Interleave uint32 `json:"interleave"`
@@ -1141,7 +1165,7 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
return nil, errors.New("unsupported address and port strategy: ", c.AddressPortStrategy)
}
happyEyeballs := &internet.HappyEyeballsConfig{Interleave: 1, PrioritizeIpv6: false, TryDelayMs: 0, MaxConcurrentTry: 4}
var happyEyeballs = &internet.HappyEyeballsConfig{Interleave: 1, PrioritizeIpv6: false, TryDelayMs: 0, MaxConcurrentTry: 4}
if c.HappyEyeballsSettings != nil {
happyEyeballs.PrioritizeIpv6 = c.HappyEyeballsSettings.PrioritizeIPv6
happyEyeballs.Interleave = c.HappyEyeballsSettings.Interleave
@@ -1217,14 +1241,20 @@ var (
}, "type", "settings")
udpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
"header-custom": func() interface{} { return new(HeaderCustomUDP) },
"mkcp-legacy": func() interface{} { return new(MkcpLegacy) },
"noise": func() interface{} { return new(NoiseMask) },
"salamander": func() interface{} { return new(Salamander) },
"sudoku": func() interface{} { return new(Sudoku) },
"xdns": func() interface{} { return new(Xdns) },
"xicmp": func() interface{} { return new(Xicmp) },
"realm": func() interface{} { return new(Realm) },
"header-custom": func() interface{} { return new(HeaderCustomUDP) },
"header-dns": func() interface{} { return new(Dns) },
"header-dtls": func() interface{} { return new(Dtls) },
"header-srtp": func() interface{} { return new(Srtp) },
"header-utp": func() interface{} { return new(Utp) },
"header-wechat": func() interface{} { return new(Wechat) },
"header-wireguard": func() interface{} { return new(Wireguard) },
"mkcp-original": func() interface{} { return new(Original) },
"mkcp-aes128gcm": func() interface{} { return new(Aes128Gcm) },
"noise": func() interface{} { return new(NoiseMask) },
"salamander": func() interface{} { return new(Salamander) },
"sudoku": func() interface{} { return new(Sudoku) },
"xdns": func() interface{} { return new(Xdns) },
"xicmp": func() interface{} { return new(Xicmp) },
}, "type", "settings")
)
@@ -1704,70 +1734,82 @@ func (c *HeaderCustomUDP) Build() (proto.Message, error) {
})
}
if c.Mode == "standalone" {
return &custom.UDPStandaloneConfig{
Client: client,
Server: server,
}, nil
} else {
return &custom.UDPConfig{
Client: client,
Server: server,
}, nil
}
return &custom.UDPConfig{
Client: client,
Server: server,
Mode: c.Mode,
}, nil
}
type MkcpLegacy struct {
Header string `json:"header"`
Value string `json:"value"`
type Dns struct {
Domain string `json:"domain"`
}
func (c *MkcpLegacy) Build() (proto.Message, error) {
if len(c.Header) == 0 {
if len(c.Value) == 0 {
return &original.Config{}, nil
} else {
return &aes128gcm.Config{Password: c.Value}, nil
}
}
switch strings.ToLower(c.Header) {
case "dns":
domain := c.Value
if len(domain) == 0 {
domain = "www.baidu.com"
}
return &header.Config{ID: 0, Domain: domain}, nil
case "dtls":
return &header.Config{ID: 1}, nil
case "srtp":
return &header.Config{ID: 2}, nil
case "utp":
return &header.Config{ID: 3}, nil
case "wechat":
return &header.Config{ID: 4}, nil
case "wireguard":
return &header.Config{ID: 5}, nil
default:
return nil, errors.New("invalid header ", c.Header)
func (c *Dns) Build() (proto.Message, error) {
config := &dns.Config{}
config.Domain = "www.baidu.com"
if len(c.Domain) > 0 {
config.Domain = c.Domain
}
return config, nil
}
type Dtls struct{}
func (c *Dtls) Build() (proto.Message, error) {
return &dtls.Config{}, nil
}
type Srtp struct{}
func (c *Srtp) Build() (proto.Message, error) {
return &srtp.Config{}, nil
}
type Utp struct{}
func (c *Utp) Build() (proto.Message, error) {
return &utp.Config{}, nil
}
type Wechat struct{}
func (c *Wechat) Build() (proto.Message, error) {
return &wechat.Config{}, nil
}
type Wireguard struct{}
func (c *Wireguard) Build() (proto.Message, error) {
return &wireguard.Config{}, nil
}
type Original struct{}
func (c *Original) Build() (proto.Message, error) {
return &original.Config{}, nil
}
type Aes128Gcm struct {
Password string `json:"password"`
}
func (c *Aes128Gcm) Build() (proto.Message, error) {
return &aes128gcm.Config{
Password: c.Password,
}, nil
}
type Salamander struct {
Password string `json:"password"`
PacketSize *Int32Range `json:"packetSize"`
Password string `json:"password"`
}
func (c *Salamander) Build() (proto.Message, error) {
if c.PacketSize != nil {
return &salamander.GeckoConfig{
Password: c.Password,
MinPacketSize: c.PacketSize.From,
MaxPacketSize: c.PacketSize.To,
}, nil
}
return &salamander.Config{
Password: c.Password,
}, nil
config := &salamander.Config{}
config.Password = c.Password
return config, nil
}
type Sudoku struct {
@@ -1843,111 +1885,23 @@ func (c *Xdns) Build() (proto.Message, error) {
}
type Xicmp struct {
DGRAM bool `json:"dgram"`
IPs []string `json:"ips"`
ListenIp string `json:"listenIp"`
Id uint16 `json:"id"`
}
func (c *Xicmp) Build() (proto.Message, error) {
for _, ip := range c.IPs {
if _, err := netip.ParseAddr(ip); err != nil {
return nil, err
}
config := &xicmp.Config{
Ip: c.ListenIp,
Id: int32(c.Id),
}
config := &xicmp.Config{
DGRAM: c.DGRAM,
IPs: c.IPs,
if config.Ip == "" {
config.Ip = "0.0.0.0"
}
return config, nil
}
type Realm struct {
Url string `json:"url"`
StunServers []string `json:"stunServers"`
TlsConfig *TLSConfig `json:"tlsConfig"`
}
func (c *Realm) Build() (proto.Message, error) {
var scheme, host, port, token, id string
var stunServers []string
var tlsConfig *tls.Config
u, err := url.Parse(c.Url)
if err != nil {
return nil, err
}
switch u.Scheme {
case "realm":
scheme = "https"
case "realm+http":
scheme = "http"
default:
return nil, errors.New("invalid scheme", u.Scheme)
}
host = u.Hostname()
if host == "" {
return nil, errors.New("invalid host", host)
}
port = u.Port()
if port == "" {
port = "443"
if scheme == "http" {
port = "80"
}
}
token, err = url.PathUnescape(u.User.String())
if err != nil {
return nil, err
}
if token == "" {
return nil, errors.New("invalid token", token)
}
id, err = url.PathUnescape(strings.TrimPrefix(u.EscapedPath(), "/"))
if err != nil {
return nil, err
}
if id == "" {
return nil, errors.New("invalid id", id)
}
if len(c.StunServers) == 0 {
return nil, errors.New("empty stunServers")
}
for _, s := range c.StunServers {
_, _, err = net.SplitHostPort(s)
if err != nil {
return nil, err
}
}
stunServers = c.StunServers
if c.TlsConfig != nil {
tc, err := c.TlsConfig.Build()
if err != nil {
return nil, err
}
tlsConfig = tc.(*tls.Config)
}
return &realm.Config{
Scheme: scheme,
Host: host,
Port: port,
Token: token,
ID: id,
StunServers: stunServers,
TlsConfig: tlsConfig,
}, nil
}
type Mask struct {
Type string `json:"type"`
Settings *json.RawMessage `json:"settings"`
@@ -2188,7 +2142,18 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
return nil, errors.New("unknown congestion control: ", c.FinalMask.QuicParams.Congestion, ", valid values: reno, bbr, brutal, force-brutal")
}
if (c.FinalMask.QuicParams.UdpHop.Interval.From != 0 && c.FinalMask.QuicParams.UdpHop.Interval.From < 5) || (c.FinalMask.QuicParams.UdpHop.Interval.To != 0 && c.FinalMask.QuicParams.UdpHop.Interval.To < 5) {
var hop *PortList
if err := json.Unmarshal(c.FinalMask.QuicParams.UdpHop.PortList, &hop); err != nil {
hop = &PortList{}
}
var inertvalMin, inertvalMax int64
if c.FinalMask.QuicParams.UdpHop.Interval != nil {
inertvalMin = int64(c.FinalMask.QuicParams.UdpHop.Interval.From)
inertvalMax = int64(c.FinalMask.QuicParams.UdpHop.Interval.To)
}
if (inertvalMin != 0 && inertvalMin < 5) || (inertvalMax != 0 && inertvalMax < 5) {
return nil, errors.New("Interval must be at least 5")
}
@@ -2225,9 +2190,9 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
BrutalUp: up,
BrutalDown: down,
UdpHop: &internet.UdpHop{
Ports: c.FinalMask.QuicParams.UdpHop.PortList.Build().Ports(),
IntervalMin: int64(c.FinalMask.QuicParams.UdpHop.Interval.From),
IntervalMax: int64(c.FinalMask.QuicParams.UdpHop.Interval.To),
Ports: hop.Build().Ports(),
IntervalMin: inertvalMin,
IntervalMax: inertvalMax,
},
InitStreamReceiveWindow: c.FinalMask.QuicParams.InitStreamReceiveWindow,
MaxStreamReceiveWindow: c.FinalMask.QuicParams.MaxStreamReceiveWindow,
+2 -1
View File
@@ -195,7 +195,8 @@ func TestHeaderCustomUDPBuild(t *testing.T) {
"mode": "standalone"
}`,
Parser: parser,
Output: &finalmaskcustom.UDPStandaloneConfig{
Output: &finalmaskcustom.UDPConfig{
Mode: "standalone",
Client: []*finalmaskcustom.UDPItem{
{
RandMax: 255,
+3 -14
View File
@@ -12,7 +12,6 @@ import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/proxy/trojan"
"google.golang.org/protobuf/proto"
)
@@ -112,7 +111,6 @@ type TrojanUserConfig struct {
// TrojanServerConfig is Inbound configuration
type TrojanServerConfig struct {
Users []*TrojanUserConfig `json:"users"`
Clients []*TrojanUserConfig `json:"clients"`
Fallbacks []*TrojanInboundFallback `json:"fallbacks"`
}
@@ -121,18 +119,13 @@ type TrojanServerConfig struct {
func (c *TrojanServerConfig) Build() (proto.Message, error) {
errors.PrintNonRemovalDeprecatedFeatureWarning("Trojan (with no Flow, etc.)", "VLESS with Flow & Seed")
if c.Clients != nil {
c.Users = c.Clients
}
config := &trojan.ServerConfig{
Users: make([]*protocol.User, len(c.Users)),
Users: make([]*protocol.User, len(c.Clients)),
}
processClient := func(idx int) error {
rawUser := c.Users[idx]
for idx, rawUser := range c.Clients {
if rawUser.Flow != "" {
return errors.PrintRemovedFeatureError(`Flow for Trojan`, ``)
return nil, errors.PrintRemovedFeatureError(`Flow for Trojan`, ``)
}
config.Users[idx] = &protocol.User{
@@ -142,10 +135,6 @@ func (c *TrojanServerConfig) Build() (proto.Message, error) {
Password: rawUser.Password,
}),
}
return nil
}
if err := task.ParallelForN(len(c.Users), processClient); err != nil {
return nil, err
}
for _, fb := range c.Fallbacks {
+1 -2
View File
@@ -1,10 +1,9 @@
package conf
import (
"strconv"
"github.com/xtls/xray-core/app/version"
"github.com/xtls/xray-core/core"
"strconv"
)
type VersionConfig struct {
+10 -22
View File
@@ -13,7 +13,6 @@ import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/common/uuid"
"github.com/xtls/xray-core/proxy/vless"
"github.com/xtls/xray-core/proxy/vless/inbound"
@@ -31,7 +30,6 @@ type VLessInboundFallback struct {
}
type VLessInboundConfig struct {
Users []json.RawMessage `json:"users"`
Clients []json.RawMessage `json:"clients"`
Decryption string `json:"decryption"`
Fallbacks []*VLessInboundFallback `json:"fallbacks"`
@@ -42,30 +40,25 @@ type VLessInboundConfig struct {
// Build implements Buildable
func (c *VLessInboundConfig) Build() (proto.Message, error) {
config := new(inbound.Config)
if c.Clients != nil {
c.Users = c.Clients
}
config.Users = make([]*protocol.User, len(c.Users))
config.Clients = make([]*protocol.User, len(c.Clients))
switch c.Flow {
case vless.XRV, "":
default:
return nil, errors.New(`VLESS "settings.flow" doesn't support "` + c.Flow + `" in this version`)
}
processClient := func(idx int) error {
rawUser := c.Users[idx]
for idx, rawUser := range c.Clients {
user := new(protocol.User)
if err := json.Unmarshal(rawUser, user); err != nil {
return errors.New(`VLESS users: invalid user`).Base(err)
return nil, errors.New(`VLESS clients: invalid user`).Base(err)
}
account := new(vless.Account)
if err := json.Unmarshal(rawUser, account); err != nil {
return errors.New(`VLESS users: invalid user`).Base(err)
return nil, errors.New(`VLESS clients: invalid user`).Base(err)
}
u, err := uuid.ParseString(account.Id)
if err != nil {
return err
return nil, err
}
account.Id = u.String()
@@ -74,7 +67,7 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
account.Flow = c.Flow
case vless.XRV:
default:
return errors.New(`VLESS users: "flow" doesn't support "` + account.Flow + `" in this version`)
return nil, errors.New(`VLESS clients: "flow" doesn't support "` + account.Flow + `" in this version`)
}
if len(account.Testseed) < 4 {
@@ -82,25 +75,20 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
}
if account.Encryption != "" {
return errors.New(`VLESS users: "encryption" should not be in inbound settings`)
return nil, errors.New(`VLESS clients: "encryption" should not be in inbound settings`)
}
if account.Reverse != nil {
if account.Reverse.Tag == "" {
return errors.New(`VLESS users: "tag" can't be empty for "reverse"`)
return nil, errors.New(`VLESS clients: "tag" can't be empty for "reverse"`)
}
if account.Reverse.Sniffing != nil { // may not be reached: error json unmarshal
return errors.New(`VLESS users: inbound's "reverse" can't have "sniffing"`)
return nil, errors.New(`VLESS clients: inbound's "reverse" can't have "sniffing"`)
}
}
user.Account = serial.ToTypedMessage(account)
config.Users[idx] = user
return nil
}
if err := task.ParallelForN(len(c.Users), processClient); err != nil {
return nil, err
config.Clients[idx] = user
}
config.Decryption = c.Decryption
+1 -1
View File
@@ -119,7 +119,7 @@ func TestVLessInbound(t *testing.T) {
}`,
Parser: loadJSON(creator),
Output: &inbound.Config{
Users: []*protocol.User{
Clients: []*protocol.User{
{
Account: serial.ToTypedMessage(&vless.Account{
Id: "27848739-7e62-4138-9fd3-098a63964b6b",
+5 -15
View File
@@ -7,7 +7,6 @@ import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/common/uuid"
"github.com/xtls/xray-core/proxy/vmess"
"github.com/xtls/xray-core/proxy/vmess/inbound"
@@ -59,8 +58,7 @@ func (c *VMessDefaultConfig) Build() *inbound.DefaultConfig {
}
type VMessInboundConfig struct {
Users []json.RawMessage `json:"users"`
Clients []json.RawMessage `json:"clients"`
Users []json.RawMessage `json:"clients"`
Defaults *VMessDefaultConfig `json:"default"`
}
@@ -74,33 +72,25 @@ func (c *VMessInboundConfig) Build() (proto.Message, error) {
config.Default = c.Defaults.Build()
}
if c.Clients != nil {
c.Users = c.Clients
}
config.User = make([]*protocol.User, len(c.Users))
processUser := func(idx int) error {
rawData := c.Users[idx]
for idx, rawData := range c.Users {
user := new(protocol.User)
if err := json.Unmarshal(rawData, user); err != nil {
return errors.New("invalid VMess user").Base(err)
return nil, errors.New("invalid VMess user").Base(err)
}
account := new(VMessAccount)
if err := json.Unmarshal(rawData, account); err != nil {
return errors.New("invalid VMess user").Base(err)
return nil, errors.New("invalid VMess user").Base(err)
}
u, err := uuid.ParseString(account.ID)
if err != nil {
return err
return nil, err
}
account.ID = u.String()
user.Account = serial.ToTypedMessage(account.Build())
config.User[idx] = user
return nil
}
if err := task.ParallelForN(len(c.Users), processUser); err != nil {
return nil, err
}
return config, nil
+2 -22
View File
@@ -173,27 +173,6 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
return nil, err
}
receiverSettings.StreamSettings = ss
// TODO: Actually implement this breaking change
protocol := ss.GetEffectiveProtocol()
if (protocol == "websocket" || protocol == "httpupgrade" || protocol == "splithttp") &&
(c.StreamSetting.SocketSettings == nil || len(c.StreamSetting.SocketSettings.TrustedXForwardedFor) == 0) {
errors.LogWarning(
context.Background(),
`====== SECURITY WARNING ======`,
"\n",
`inbound "`, c.Tag, `" using `, protocol, ` has not configured "sockopt.trustedXForwardedFor".`,
"\n",
`THIS IS VERY INSECURE!!!`,
"\n",
`For compatibility, Xray still allows this for now and still trusts X-Forwarded-For implicitly.`,
"\n",
`Please configure "sockopt.trustedXForwardedFor" immediately.`,
"\n",
`In future versions, this option must be explicitly set.`,
"\n",
`====== SECURITY WARNING ======`,
)
}
if strings.Contains(ss.SecurityType, "reality") && (receiverSettings.PortList == nil ||
len(receiverSettings.PortList.Ports()) != 1 || receiverSettings.PortList.Ports()[0] != 443) {
errors.LogWarning(context.Background(), `REALITY: Listening on non-443 ports may get your IP blocked by the GFW`)
@@ -286,7 +265,7 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
if c.SendThrough != nil {
address := ParseSendThough(c.SendThrough)
// Check if CIDR exists
//Check if CIDR exists
if strings.Contains(*c.SendThrough, "/") {
senderSettings.ViaCidr = strings.Split(*c.SendThrough, "/")[1]
} else {
@@ -470,6 +449,7 @@ func (c *Config) Override(o *Config, fn string) {
c.InboundConfigs = append(c.InboundConfigs, o.InboundConfigs[i])
errors.LogInfo(context.Background(), "[", fn, "] appended inbound with tag: ", o.InboundConfigs[i].Tag)
}
}
}
+19 -62
View File
@@ -12,16 +12,7 @@ import (
"strings"
)
var (
directory = flag.String("pwd", "", "Working directory of Xray vformat.")
action = flag.String("mode", "format", "Execution mode. Default is 'format'.\n'format' formatting source files and save changes to files.\n'check' list all paths of improper formatted file.\n'dryrun' formatting source files and shows all diffs, but will not make any changes to files.")
)
var (
isCheck bool
isDryrun bool
isFormat bool
)
var directory = flag.String("pwd", "", "Working directory of Xray vformat.")
// envFile returns the name of the Go environment configuration file.
// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166
@@ -99,10 +90,9 @@ func Run(binary string, args []string) ([]byte, error) {
return output, nil
}
func RunMany(binary string, args, files []string) bool {
fmt.Println("Processing with", binary, args, "...")
func RunMany(binary string, args, files []string) {
fmt.Println("Processing...")
formatRequired := false
maxTasks := make(chan struct{}, runtime.NumCPU())
for _, file := range files {
maxTasks <- struct{}{}
@@ -112,12 +102,10 @@ func RunMany(binary string, args, files []string) bool {
fmt.Println(err)
} else if len(output) > 0 {
fmt.Println(string(output))
formatRequired = true
}
<-maxTasks
}(file)
}
return formatRequired
}
func main() {
@@ -136,19 +124,6 @@ func main() {
*directory = filepath.Join(pwd, *directory)
}
switch *action {
case "format":
isFormat = true
case "check":
isCheck = true
case "dryrun":
isCheck = true
isDryrun = true
default:
fmt.Println("Unrecognized 'mode'. Will format all source files and save changes.")
isFormat = true
}
pwd := *directory
GOBIN := GetGOBIN()
binPath := os.Getenv("PATH")
@@ -161,6 +136,7 @@ func main() {
suffix = ".exe"
}
gofmt := "gofumpt" + suffix
goimports := "gci" + suffix
if gofmtPath, err := exec.LookPath(gofmt); err != nil {
fmt.Println("Can not find", gofmt, "in system path or current working directory.")
@@ -169,6 +145,13 @@ func main() {
gofmt = gofmtPath
}
if goimportsPath, err := exec.LookPath(goimports); err != nil {
fmt.Println("Can not find", goimports, "in system path or current working directory.")
os.Exit(1)
} else {
goimports = goimportsPath
}
rawFilesSlice := make([]string, 0, 1000)
walkErr := filepath.Walk(pwd, func(path string, info os.FileInfo, err error) error {
if err != nil {
@@ -196,41 +179,15 @@ func main() {
os.Exit(1)
}
if isFormat {
gofmtArgs := []string{
"-l", "-e", "-w",
}
fmt.Println("Formatting Go source files...")
RunMany(gofmt, gofmtArgs, rawFilesSlice)
fmt.Println("Do NOT forget to commit file changes.")
gofmtArgs := []string{
"-s", "-l", "-e", "-w",
}
if isCheck {
gofmtListArgs := []string{
"-l", "-e",
}
fmt.Println("Checking files thar are not properly formatted...")
formatRequired := RunMany(gofmt, gofmtListArgs, rawFilesSlice)
if formatRequired {
fmt.Println("Format problem(s) found.")
}
if isDryrun {
if formatRequired {
gofmtShowArgs := []string{
"-d", "-e",
}
RunMany(gofmt, gofmtShowArgs, rawFilesSlice)
}
}
if formatRequired {
fmt.Println("Please run 'go install -v mvdan.cc/gofumpt@latest', then run 'go run ./infra/vformat/main.go' to format the Go source files.")
os.Exit(1)
} else {
fmt.Println("All Go source file format check has been passed.")
}
goimportsArgs := []string{
"write",
}
RunMany(gofmt, gofmtArgs, rawFilesSlice)
RunMany(goimports, goimportsArgs, rawFilesSlice)
fmt.Println("Do NOT forget to commit file changes.")
}
+1
View File
@@ -59,6 +59,7 @@ func executeBalancerInfo(cmd *base.Command, args []string) {
}
showBalancerInfo(resp.Balancer)
}
func showBalancerInfo(b *routerService.BalancerMsg) {
+2 -3
View File
@@ -62,8 +62,7 @@ func addInboundUserAction(ctx context.Context, client handlerService.HandlerServ
Operation: cserial.ToTypedMessage(
&handlerService.AddUserOperation{
User: user,
},
),
}),
})
return err
}
@@ -81,7 +80,7 @@ func extractInboundUsers(inb *core.InboundHandlerConfig) []*protocol.User {
case *vmessin.Config:
return ty.User
case *vlessin.Config:
return ty.Users
return ty.Clients
case *trojan.ServerConfig:
return ty.Users
case *shadowsocks.ServerConfig:

Some files were not shown because too many files have changed in this diff Show More