mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-26 16:58:47 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f2fe54d0f | ||
|
|
fe1c2f9bb8 | ||
|
|
643fa66adc | ||
|
|
b585551ddc | ||
|
|
3519dfecbd | ||
|
|
df261e4479 | ||
|
|
61cad5ec8b | ||
|
|
a642a190ed | ||
|
|
60e2a0c502 | ||
|
|
7d3e44fee2 | ||
|
|
9927942aaa | ||
|
|
a308ded2e6 | ||
|
|
7741e9e77e | ||
|
|
d562d8947d | ||
|
|
dbb1ea30ba | ||
|
|
efc9e6da62 | ||
|
|
8267cf953a | ||
|
|
24e6f6d551 | ||
|
|
dcdfc57ccd | ||
|
|
3461c511aa | ||
|
|
c412e77a9b | ||
|
|
ccb69ea5e2 | ||
|
|
52a412d9e2 | ||
|
|
18a1b5042a | ||
|
|
c26d2eda24 | ||
|
|
a1bf968be9 | ||
|
|
c037ccd98d | ||
|
|
37ceb8b4b6 | ||
|
|
fd2ca74822 | ||
|
|
47cfe9994a | ||
|
|
3e2f040cd8 | ||
|
|
c7245c0336 | ||
|
|
eef6e63bc1 | ||
|
|
6ce8dc53e7 | ||
|
|
01a034be53 | ||
|
|
de2caf3cef | ||
|
|
cecc88f43c | ||
|
|
cd4ce973e9 | ||
|
|
fc7b980636 | ||
|
|
8ee131cbbb | ||
|
|
2776ea6d74 | ||
|
|
5e245b082e | ||
|
|
d9c54026c5 | ||
|
|
c1958dba04 | ||
|
|
540b9070f5 | ||
|
|
ada99a4eb0 | ||
|
|
65458e919f | ||
|
|
aa3d6589da | ||
|
|
25c11e2d2b | ||
|
|
dffc7ada5e | ||
|
|
77f98eba09 | ||
|
|
f124daf5a3 | ||
|
|
598bde7412 | ||
|
|
9b373e39ca | ||
|
|
c7e569b037 | ||
|
|
f02a357861 | ||
|
|
5fe6d6217a | ||
|
|
0604ffa957 | ||
|
|
d3f1a24285 | ||
|
|
2323273e37 | ||
|
|
09107b71dc | ||
|
|
7021606ad3 | ||
|
|
7d214f8b09 | ||
|
|
8b419d833d | ||
|
|
a12801c13b | ||
|
|
a000371b2a | ||
|
|
bc6e966af8 | ||
|
|
fc5620de98 | ||
|
|
b02bdcf4cc | ||
|
|
2b329b3675 | ||
|
|
5ca6f4b7d4 | ||
|
|
18e283909c | ||
|
|
6ab123bf8f | ||
|
|
4aba687dd3 | ||
|
|
5b1b41058e | ||
|
|
6e3322d219 | ||
|
|
1d8eb81d70 | ||
|
|
e78d8ef184 | ||
|
|
6ce924ad56 | ||
|
|
035d438979 | ||
|
|
50231eaff9 | ||
|
|
1f74c480d6 | ||
|
|
af7eb68028 | ||
|
|
35387572e0 | ||
|
|
8f15190c23 | ||
|
|
64fada32b5 | ||
|
|
0bafca9486 | ||
|
|
d5bc58dc6b | ||
|
|
c18b39ed80 | ||
|
|
e2ad0acf60 | ||
|
|
c320e89108 | ||
|
|
412898fed7 | ||
|
|
5c62d50d43 |
@@ -37,6 +37,7 @@ 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/09_metrics.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/10_observatory.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/11_geodata.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/12_env.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
|
||||||
|
|
||||||
# Create log files
|
# Create log files
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ 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/09_metrics.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/10_observatory.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/11_geodata.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/12_env.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
|
||||||
|
|
||||||
# Create log files
|
# Create log files
|
||||||
|
|||||||
@@ -64,6 +64,14 @@ jobs:
|
|||||||
echo "Latest: '$LATEST'."
|
echo "Latest: '$LATEST'."
|
||||||
echo "LATEST=$LATEST" >>${GITHUB_ENV}
|
echo "LATEST=$LATEST" >>${GITHUB_ENV}
|
||||||
|
|
||||||
|
NEWEST=false
|
||||||
|
if [[ "${{ github.event_name }}" == "release" ]]; then
|
||||||
|
NEWEST=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Newest: '$NEWEST'."
|
||||||
|
echo "NEWEST=$NEWEST" >>${GITHUB_ENV}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
@@ -74,7 +82,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@v4
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v4.6.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -124,6 +132,13 @@ jobs:
|
|||||||
${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "${{ env.NEWEST }}" == "true" ]]; then
|
||||||
|
echo "Adding 'pre-release' tag to manifest: '${{ env.FULL_IMAGE_NAME }}:pre-release'."
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--tag ${{ env.FULL_IMAGE_NAME }}:pre-release \
|
||||||
|
${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Inspect image
|
- name: Inspect image
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
||||||
@@ -131,3 +146,7 @@ jobs:
|
|||||||
if [[ "${{ env.LATEST }}" == "true" ]]; then
|
if [[ "${{ env.LATEST }}" == "true" ]]; then
|
||||||
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:latest
|
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:latest
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "${{ env.NEWEST }}" == "true" ]]; then
|
||||||
|
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:pre-release
|
||||||
|
fi
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ jobs:
|
|||||||
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v7
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ jobs:
|
|||||||
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v7
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|||||||
@@ -61,15 +61,13 @@ jobs:
|
|||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v7
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
cache: false
|
cache: false
|
||||||
- name: Check Format
|
- name: Check Format
|
||||||
run: |
|
run: go run ./infra/vformat/main.go -mode check -pwd ./
|
||||||
go install -v mvdan.cc/gofumpt@latest
|
|
||||||
go run ./infra/vformat/main.go -mode check -pwd ./
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
needs: check-assets
|
needs: check-assets
|
||||||
@@ -85,7 +83,7 @@ jobs:
|
|||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v7
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
|
|||||||
p := d.policy.ForLevel(user.Level)
|
p := d.policy.ForLevel(user.Level)
|
||||||
if p.Stats.UserUplink {
|
if p.Stats.UserUplink {
|
||||||
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
|
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
|
||||||
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {
|
if c, _ := d.stats.GetOrRegisterCounter(name); c != nil {
|
||||||
inboundLink.Writer = &SizeStatWriter{
|
inboundLink.Writer = &SizeStatWriter{
|
||||||
Counter: c,
|
Counter: c,
|
||||||
Writer: inboundLink.Writer,
|
Writer: inboundLink.Writer,
|
||||||
@@ -171,7 +171,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
|
|||||||
}
|
}
|
||||||
if p.Stats.UserDownlink {
|
if p.Stats.UserDownlink {
|
||||||
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
|
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
|
||||||
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {
|
if c, _ := d.stats.GetOrRegisterCounter(name); c != nil {
|
||||||
outboundLink.Writer = &SizeStatWriter{
|
outboundLink.Writer = &SizeStatWriter{
|
||||||
Counter: c,
|
Counter: c,
|
||||||
Writer: outboundLink.Writer,
|
Writer: outboundLink.Writer,
|
||||||
@@ -200,13 +200,13 @@ func WrapLink(ctx context.Context, policyManager policy.Manager, statsManager st
|
|||||||
p := policyManager.ForLevel(user.Level)
|
p := policyManager.ForLevel(user.Level)
|
||||||
if p.Stats.UserUplink {
|
if p.Stats.UserUplink {
|
||||||
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
|
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
|
||||||
if c, _ := stats.GetOrRegisterCounter(statsManager, name); c != nil {
|
if c, _ := statsManager.GetOrRegisterCounter(name); c != nil {
|
||||||
link.Reader.(*buf.TimeoutWrapperReader).Counter = c
|
link.Reader.(*buf.TimeoutWrapperReader).Counter = c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if p.Stats.UserDownlink {
|
if p.Stats.UserDownlink {
|
||||||
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
|
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
|
||||||
if c, _ := stats.GetOrRegisterCounter(statsManager, name); c != nil {
|
if c, _ := statsManager.GetOrRegisterCounter(name); c != nil {
|
||||||
link.Writer = &SizeStatWriter{
|
link.Writer = &SizeStatWriter{
|
||||||
Counter: c,
|
Counter: c,
|
||||||
Writer: link.Writer,
|
Writer: link.Writer,
|
||||||
@@ -223,7 +223,7 @@ func WrapLink(ctx context.Context, policyManager policy.Manager, statsManager st
|
|||||||
|
|
||||||
func trackOnlineIP(ctx context.Context, sm stats.Manager, email, ip string) {
|
func trackOnlineIP(ctx context.Context, sm stats.Manager, email, ip string) {
|
||||||
name := "user>>>" + email + ">>>online"
|
name := "user>>>" + email + ">>>online"
|
||||||
if om, _ := stats.GetOrRegisterOnlineMap(sm, name); om != nil {
|
if om, _ := sm.GetOrRegisterOnlineMap(name); om != nil {
|
||||||
om.AddIP(ip)
|
om.AddIP(ip)
|
||||||
context.AfterFunc(ctx, func() { om.RemoveIP(ip) })
|
context.AfterFunc(ctx, func() { om.RemoveIP(ip) })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if fakeDNSEngine == nil {
|
if fakeDNSEngine == nil {
|
||||||
errNotInit := errors.New("FakeDNSEngine is not initialized, but such a sniffer is used").AtError()
|
errNotInit := errors.New("FakeDNSEngine is not initialized, but such a sniffer is used")
|
||||||
return protocolSnifferWithMetadata{}, errNotInit
|
return protocolSnifferWithMetadata{}, errNotInit
|
||||||
}
|
}
|
||||||
return protocolSnifferWithMetadata{protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {
|
return protocolSnifferWithMetadata{protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ func toNetIP(addrs []net.Address) ([]net.IP, error) {
|
|||||||
if addr.Family().IsIP() {
|
if addr.Family().IsIP() {
|
||||||
ips = append(ips, addr.IP())
|
ips = append(ips, addr.IP())
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("Failed to convert address", addr, "to Net IP.").AtWarning()
|
return nil, errors.New("Failed to convert address", addr, "to Net IP.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ips, nil
|
return ips, nil
|
||||||
|
|||||||
@@ -188,10 +188,10 @@ func parseResponse(payload []byte) (*IPRecord, error) {
|
|||||||
var parser dnsmessage.Parser
|
var parser dnsmessage.Parser
|
||||||
h, err := parser.Start(payload)
|
h, err := parser.Start(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to parse DNS response").Base(err).AtWarning()
|
return nil, errors.New("failed to parse DNS response").Base(err)
|
||||||
}
|
}
|
||||||
if err := parser.SkipAllQuestions(); err != nil {
|
if err := parser.SkipAllQuestions(); err != nil {
|
||||||
return nil, errors.New("failed to skip questions in DNS response").Base(err).AtWarning()
|
return nil, errors.New("failed to skip questions in DNS response").Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func NewFakeDNSHolder() (*Holder, error) {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
if fkdns, err = NewFakeDNSHolderConfigOnly(nil); err != nil {
|
if fkdns, err = NewFakeDNSHolderConfigOnly(nil); err != nil {
|
||||||
return nil, errors.New("Unable to create Fake Dns Engine").Base(err).AtError()
|
return nil, errors.New("Unable to create Fake Dns Engine").Base(err)
|
||||||
}
|
}
|
||||||
err = fkdns.initialize(dns.FakeIPv4Pool, 65535)
|
err = fkdns.initialize(dns.FakeIPv4Pool, 65535)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -80,13 +80,13 @@ func (fkdns *Holder) initialize(ipPoolCidr string, lruSize int) error {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
if _, ipRange, err = net.ParseCIDR(ipPoolCidr); err != nil {
|
if _, ipRange, err = net.ParseCIDR(ipPoolCidr); err != nil {
|
||||||
return errors.New("Unable to parse CIDR for Fake DNS IP assignment").Base(err).AtError()
|
return errors.New("Unable to parse CIDR for Fake DNS IP assignment").Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ones, bits := ipRange.Mask.Size()
|
ones, bits := ipRange.Mask.Size()
|
||||||
rooms := bits - ones
|
rooms := bits - ones
|
||||||
if math.Log2(float64(lruSize)) >= float64(rooms) {
|
if math.Log2(float64(lruSize)) >= float64(rooms) {
|
||||||
return errors.New("LRU size is bigger than subnet size").AtError()
|
return errors.New("LRU size is bigger than subnet size")
|
||||||
}
|
}
|
||||||
fkdns.domainToIP = cache.NewLru(lruSize)
|
fkdns.domainToIP = cache.NewLru(lruSize)
|
||||||
fkdns.ipRange = ipRange
|
fkdns.ipRange = ipRange
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ func NewServer(ctx context.Context, dest net.Destination, dispatcher routing.Dis
|
|||||||
if dest.Network == net.Network_UDP { // UDP classic DNS mode
|
if dest.Network == net.Network_UDP { // UDP classic DNS mode
|
||||||
return NewClassicNameServer(dest, dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP), nil
|
return NewClassicNameServer(dest, dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP), nil
|
||||||
}
|
}
|
||||||
return nil, errors.New("No available name server could be created from ", dest).AtWarning()
|
return nil, errors.New("No available name server could be created from ", dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient creates a DNS client managing a name server with client IP, domain rules and expected IPs.
|
// NewClient creates a DNS client managing a name server with client IP, domain rules and expected IPs.
|
||||||
@@ -102,7 +102,7 @@ func NewClient(
|
|||||||
// Create a new server for each client for now
|
// Create a new server for each client for now
|
||||||
server, err := NewServer(ctx, ns.Address.AsDestination(), dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP)
|
server, err := NewServer(ctx, ns.Address.AsDestination(), dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("failed to create nameserver").Base(err).AtWarning()
|
return errors.New("failed to create nameserver").Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, isLocalDNS := server.(*LocalNameServer)
|
_, isLocalDNS := server.(*LocalNameServer)
|
||||||
@@ -113,7 +113,7 @@ func NewClient(
|
|||||||
if len(ns.ExpectedIp) > 0 {
|
if len(ns.ExpectedIp) > 0 {
|
||||||
expectedMatcher, err = geodata.IPReg.BuildIPMatcher(ns.ExpectedIp)
|
expectedMatcher, err = geodata.IPReg.BuildIPMatcher(ns.ExpectedIp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("failed to create expected ip matcher").Base(err).AtWarning()
|
return errors.New("failed to create expected ip matcher").Base(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ func NewClient(
|
|||||||
if len(ns.UnexpectedIp) > 0 {
|
if len(ns.UnexpectedIp) > 0 {
|
||||||
unexpectedMatcher, err = geodata.IPReg.BuildIPMatcher(ns.UnexpectedIp)
|
unexpectedMatcher, err = geodata.IPReg.BuildIPMatcher(ns.UnexpectedIp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("failed to create unexpected ip matcher").Base(err).AtWarning()
|
return errors.New("failed to create unexpected ip matcher").Base(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func (s *FakeDNSServer) IsDisableCache() bool {
|
|||||||
|
|
||||||
func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, opt dns.IPOption) ([]net.IP, uint32, error) {
|
func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, opt dns.IPOption) ([]net.IP, uint32, error) {
|
||||||
if f.fakeDNSEngine == nil {
|
if f.fakeDNSEngine == nil {
|
||||||
return nil, 0, errors.New("Unable to locate a fake DNS Engine").AtError()
|
return nil, 0, errors.New("Unable to locate a fake DNS Engine")
|
||||||
}
|
}
|
||||||
|
|
||||||
var ips []net.Address
|
var ips []net.Address
|
||||||
@@ -39,7 +39,7 @@ func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, opt dns.IPOp
|
|||||||
|
|
||||||
netIP, err := toNetIP(ips)
|
netIP, err := toNetIP(ips)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, errors.New("Unable to convert IP to net ip").Base(err).AtError()
|
return nil, 0, errors.New("Unable to convert IP to net ip").Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
errors.LogInfo(ctx, f.Name(), " got answer: ", domain, " -> ", ips)
|
errors.LogInfo(ctx, f.Name(), " got answer: ", domain, " -> ", ips)
|
||||||
|
|||||||
+6
-2
@@ -89,10 +89,10 @@ func (g *Instance) startInternal() error {
|
|||||||
g.active = true
|
g.active = true
|
||||||
|
|
||||||
if err := g.initAccessLogger(); err != nil {
|
if err := g.initAccessLogger(); err != nil {
|
||||||
return errors.New("failed to initialize access logger").Base(err).AtWarning()
|
return errors.New("failed to initialize access logger").Base(err)
|
||||||
}
|
}
|
||||||
if err := g.initErrorLogger(); err != nil {
|
if err := g.initErrorLogger(); err != nil {
|
||||||
return errors.New("failed to initialize error logger").Base(err).AtWarning()
|
return errors.New("failed to initialize error logger").Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -141,6 +141,10 @@ func (g *Instance) Handle(msg log.Message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Instance) Severity() log.Severity {
|
||||||
|
return g.config.ErrorLogLevel
|
||||||
|
}
|
||||||
|
|
||||||
// Close implements common.Closable.Close().
|
// Close implements common.Closable.Close().
|
||||||
func (g *Instance) Close() error {
|
func (g *Instance) Close() error {
|
||||||
errors.LogDebug(context.Background(), "Logger closing")
|
errors.LogDebug(context.Background(), "Logger closing")
|
||||||
|
|||||||
@@ -78,6 +78,12 @@ func (o *Observer) background() {
|
|||||||
sleepTime = time.Duration(o.config.ProbeInterval)
|
sleepTime = time.Duration(o.config.ProbeInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(outbounds) == 0 {
|
||||||
|
errors.LogWarning(o.ctx, "no outbound matches subjectSelector ", o.config.SubjectSelector)
|
||||||
|
time.Sleep(sleepTime)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if !o.config.EnableConcurrency {
|
if !o.config.EnableConcurrency {
|
||||||
sort.Strings(outbounds)
|
sort.Strings(outbounds)
|
||||||
for _, v := range outbounds {
|
for _, v := range outbounds {
|
||||||
|
|||||||
+11
-22
@@ -330,7 +330,6 @@ type SenderConfig struct {
|
|||||||
// Send traffic through the given IP. Only IP is allowed.
|
// Send traffic through the given IP. Only IP is allowed.
|
||||||
Via *net.IPOrDomain `protobuf:"bytes,1,opt,name=via,proto3" json:"via,omitempty"`
|
Via *net.IPOrDomain `protobuf:"bytes,1,opt,name=via,proto3" json:"via,omitempty"`
|
||||||
StreamSettings *internet.StreamConfig `protobuf:"bytes,2,opt,name=stream_settings,json=streamSettings,proto3" json:"stream_settings,omitempty"`
|
StreamSettings *internet.StreamConfig `protobuf:"bytes,2,opt,name=stream_settings,json=streamSettings,proto3" json:"stream_settings,omitempty"`
|
||||||
ProxySettings *internet.ProxyConfig `protobuf:"bytes,3,opt,name=proxy_settings,json=proxySettings,proto3" json:"proxy_settings,omitempty"`
|
|
||||||
MultiplexSettings *MultiplexingConfig `protobuf:"bytes,4,opt,name=multiplex_settings,json=multiplexSettings,proto3" json:"multiplex_settings,omitempty"`
|
MultiplexSettings *MultiplexingConfig `protobuf:"bytes,4,opt,name=multiplex_settings,json=multiplexSettings,proto3" json:"multiplex_settings,omitempty"`
|
||||||
ViaCidr string `protobuf:"bytes,5,opt,name=via_cidr,json=viaCidr,proto3" json:"via_cidr,omitempty"`
|
ViaCidr string `protobuf:"bytes,5,opt,name=via_cidr,json=viaCidr,proto3" json:"via_cidr,omitempty"`
|
||||||
TargetStrategy internet.DomainStrategy `protobuf:"varint,6,opt,name=target_strategy,json=targetStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"target_strategy,omitempty"`
|
TargetStrategy internet.DomainStrategy `protobuf:"varint,6,opt,name=target_strategy,json=targetStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"target_strategy,omitempty"`
|
||||||
@@ -382,13 +381,6 @@ func (x *SenderConfig) GetStreamSettings() *internet.StreamConfig {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *SenderConfig) GetProxySettings() *internet.ProxyConfig {
|
|
||||||
if x != nil {
|
|
||||||
return x.ProxySettings
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *SenderConfig) GetMultiplexSettings() *MultiplexingConfig {
|
func (x *SenderConfig) GetMultiplexSettings() *MultiplexingConfig {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.MultiplexSettings
|
return x.MultiplexSettings
|
||||||
@@ -506,14 +498,13 @@ const file_app_proxyman_config_proto_rawDesc = "" +
|
|||||||
"\x03tag\x18\x01 \x01(\tR\x03tag\x12M\n" +
|
"\x03tag\x18\x01 \x01(\tR\x03tag\x12M\n" +
|
||||||
"\x11receiver_settings\x18\x02 \x01(\v2 .xray.common.serial.TypedMessageR\x10receiverSettings\x12G\n" +
|
"\x11receiver_settings\x18\x02 \x01(\v2 .xray.common.serial.TypedMessageR\x10receiverSettings\x12G\n" +
|
||||||
"\x0eproxy_settings\x18\x03 \x01(\v2 .xray.common.serial.TypedMessageR\rproxySettings\"\x10\n" +
|
"\x0eproxy_settings\x18\x03 \x01(\v2 .xray.common.serial.TypedMessageR\rproxySettings\"\x10\n" +
|
||||||
"\x0eOutboundConfig\"\x9d\x03\n" +
|
"\x0eOutboundConfig\"\xd6\x02\n" +
|
||||||
"\fSenderConfig\x12-\n" +
|
"\fSenderConfig\x12-\n" +
|
||||||
"\x03via\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\x03via\x12N\n" +
|
"\x03via\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\x03via\x12N\n" +
|
||||||
"\x0fstream_settings\x18\x02 \x01(\v2%.xray.transport.internet.StreamConfigR\x0estreamSettings\x12K\n" +
|
"\x0fstream_settings\x18\x02 \x01(\v2%.xray.transport.internet.StreamConfigR\x0estreamSettings\x12T\n" +
|
||||||
"\x0eproxy_settings\x18\x03 \x01(\v2$.xray.transport.internet.ProxyConfigR\rproxySettings\x12T\n" +
|
|
||||||
"\x12multiplex_settings\x18\x04 \x01(\v2%.xray.app.proxyman.MultiplexingConfigR\x11multiplexSettings\x12\x19\n" +
|
"\x12multiplex_settings\x18\x04 \x01(\v2%.xray.app.proxyman.MultiplexingConfigR\x11multiplexSettings\x12\x19\n" +
|
||||||
"\bvia_cidr\x18\x05 \x01(\tR\aviaCidr\x12P\n" +
|
"\bvia_cidr\x18\x05 \x01(\tR\aviaCidr\x12P\n" +
|
||||||
"\x0ftarget_strategy\x18\x06 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0etargetStrategy\"\xa4\x01\n" +
|
"\x0ftarget_strategy\x18\x06 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0etargetStrategyJ\x04\b\x03\x10\x04\"\xa4\x01\n" +
|
||||||
"\x12MultiplexingConfig\x12\x18\n" +
|
"\x12MultiplexingConfig\x12\x18\n" +
|
||||||
"\aenabled\x18\x01 \x01(\bR\aenabled\x12 \n" +
|
"\aenabled\x18\x01 \x01(\bR\aenabled\x12 \n" +
|
||||||
"\vconcurrency\x18\x02 \x01(\x05R\vconcurrency\x12(\n" +
|
"\vconcurrency\x18\x02 \x01(\x05R\vconcurrency\x12(\n" +
|
||||||
@@ -548,8 +539,7 @@ var file_app_proxyman_config_proto_goTypes = []any{
|
|||||||
(*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain
|
(*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain
|
||||||
(*internet.StreamConfig)(nil), // 11: xray.transport.internet.StreamConfig
|
(*internet.StreamConfig)(nil), // 11: xray.transport.internet.StreamConfig
|
||||||
(*serial.TypedMessage)(nil), // 12: xray.common.serial.TypedMessage
|
(*serial.TypedMessage)(nil), // 12: xray.common.serial.TypedMessage
|
||||||
(*internet.ProxyConfig)(nil), // 13: xray.transport.internet.ProxyConfig
|
(internet.DomainStrategy)(0), // 13: xray.transport.internet.DomainStrategy
|
||||||
(internet.DomainStrategy)(0), // 14: xray.transport.internet.DomainStrategy
|
|
||||||
}
|
}
|
||||||
var file_app_proxyman_config_proto_depIdxs = []int32{
|
var file_app_proxyman_config_proto_depIdxs = []int32{
|
||||||
7, // 0: xray.app.proxyman.SniffingConfig.domains_excluded:type_name -> xray.common.geodata.DomainRule
|
7, // 0: xray.app.proxyman.SniffingConfig.domains_excluded:type_name -> xray.common.geodata.DomainRule
|
||||||
@@ -562,14 +552,13 @@ var file_app_proxyman_config_proto_depIdxs = []int32{
|
|||||||
12, // 7: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage
|
12, // 7: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage
|
||||||
10, // 8: xray.app.proxyman.SenderConfig.via:type_name -> xray.common.net.IPOrDomain
|
10, // 8: xray.app.proxyman.SenderConfig.via:type_name -> xray.common.net.IPOrDomain
|
||||||
11, // 9: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
|
11, // 9: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
|
||||||
13, // 10: xray.app.proxyman.SenderConfig.proxy_settings:type_name -> xray.transport.internet.ProxyConfig
|
6, // 10: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig
|
||||||
6, // 11: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig
|
13, // 11: xray.app.proxyman.SenderConfig.target_strategy:type_name -> xray.transport.internet.DomainStrategy
|
||||||
14, // 12: xray.app.proxyman.SenderConfig.target_strategy:type_name -> xray.transport.internet.DomainStrategy
|
12, // [12:12] is the sub-list for method output_type
|
||||||
13, // [13:13] is the sub-list for method output_type
|
12, // [12:12] is the sub-list for method input_type
|
||||||
13, // [13:13] is the sub-list for method input_type
|
12, // [12:12] is the sub-list for extension type_name
|
||||||
13, // [13:13] is the sub-list for extension type_name
|
12, // [12:12] is the sub-list for extension extendee
|
||||||
13, // [13:13] is the sub-list for extension extendee
|
0, // [0:12] is the sub-list for field type_name
|
||||||
0, // [0:13] is the sub-list for field type_name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_app_proxyman_config_proto_init() }
|
func init() { file_app_proxyman_config_proto_init() }
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ message SenderConfig {
|
|||||||
// Send traffic through the given IP. Only IP is allowed.
|
// Send traffic through the given IP. Only IP is allowed.
|
||||||
xray.common.net.IPOrDomain via = 1;
|
xray.common.net.IPOrDomain via = 1;
|
||||||
xray.transport.internet.StreamConfig stream_settings = 2;
|
xray.transport.internet.StreamConfig stream_settings = 2;
|
||||||
xray.transport.internet.ProxyConfig proxy_settings = 3;
|
reserved 3;
|
||||||
MultiplexingConfig multiplex_settings = 4;
|
MultiplexingConfig multiplex_settings = 4;
|
||||||
string via_cidr = 5;
|
string via_cidr = 5;
|
||||||
xray.transport.internet.DomainStrategy target_strategy = 6;
|
xray.transport.internet.DomainStrategy target_strategy = 6;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
|
|||||||
if len(tag) > 0 && policy.ForSystem().Stats.InboundUplink {
|
if len(tag) > 0 && policy.ForSystem().Stats.InboundUplink {
|
||||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||||
name := "inbound>>>" + tag + ">>>traffic>>>uplink"
|
name := "inbound>>>" + tag + ">>>traffic>>>uplink"
|
||||||
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
c, _ := statsManager.GetOrRegisterCounter(name)
|
||||||
if c != nil {
|
if c != nil {
|
||||||
uplinkCounter = c
|
uplinkCounter = c
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,7 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
|
|||||||
if len(tag) > 0 && policy.ForSystem().Stats.InboundDownlink {
|
if len(tag) > 0 && policy.ForSystem().Stats.InboundDownlink {
|
||||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||||
name := "inbound>>>" + tag + ">>>traffic>>>downlink"
|
name := "inbound>>>" + tag + ">>>traffic>>>downlink"
|
||||||
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
c, _ := statsManager.GetOrRegisterCounter(name)
|
||||||
if c != nil {
|
if c != nil {
|
||||||
downlinkCounter = c
|
downlinkCounter = c
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
|||||||
}
|
}
|
||||||
mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings)
|
mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to parse stream config").Base(err).AtWarning()
|
return nil, errors.New("failed to parse stream config").Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newCtx := session.ContextWithInbound(ctx, &session.Inbound{Tag: tag, Source: src})
|
newCtx := session.ContextWithInbound(ctx, &session.Inbound{Tag: tag, Source: src})
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ func NewHandler(ctx context.Context, config *core.InboundHandlerConfig) (inbound
|
|||||||
|
|
||||||
receiverSettings, ok := rawReceiverSettings.(*proxyman.ReceiverConfig)
|
receiverSettings, ok := rawReceiverSettings.(*proxyman.ReceiverConfig)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("not a ReceiverConfig").AtError()
|
return nil, errors.New("not a ReceiverConfig")
|
||||||
}
|
}
|
||||||
|
|
||||||
streamSettings := receiverSettings.StreamSettings
|
streamSettings := receiverSettings.StreamSettings
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ func (w *tcpWorker) Start() error {
|
|||||||
go w.callback(conn)
|
go w.callback(conn)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("failed to listen TCP on ", w.port).AtWarning().Base(err)
|
return errors.New("failed to listen TCP on ", w.port).Base(err)
|
||||||
}
|
}
|
||||||
w.hub = hub
|
w.hub = hub
|
||||||
return nil
|
return nil
|
||||||
@@ -528,7 +528,7 @@ func (w *dsWorker) Start() error {
|
|||||||
go w.callback(conn)
|
go w.callback(conn)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("failed to listen Unix Domain Socket on ", w.address).AtWarning().Base(err)
|
return errors.New("failed to listen Unix Domain Socket on ", w.address).Base(err)
|
||||||
}
|
}
|
||||||
w.hub = hub
|
w.hub = hub
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/mux"
|
"github.com/xtls/xray-core/common/mux"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/net/cnc"
|
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
"github.com/xtls/xray-core/common/session"
|
"github.com/xtls/xray-core/common/session"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
@@ -26,8 +25,6 @@ import (
|
|||||||
"github.com/xtls/xray-core/transport"
|
"github.com/xtls/xray-core/transport"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
"github.com/xtls/xray-core/transport/internet/stat"
|
"github.com/xtls/xray-core/transport/internet/stat"
|
||||||
"github.com/xtls/xray-core/transport/internet/tls"
|
|
||||||
"github.com/xtls/xray-core/transport/pipe"
|
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,7 +36,7 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
|
|||||||
if len(tag) > 0 && policy.ForSystem().Stats.OutboundUplink {
|
if len(tag) > 0 && policy.ForSystem().Stats.OutboundUplink {
|
||||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||||
name := "outbound>>>" + tag + ">>>traffic>>>uplink"
|
name := "outbound>>>" + tag + ">>>traffic>>>uplink"
|
||||||
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
c, _ := statsManager.GetOrRegisterCounter(name)
|
||||||
if c != nil {
|
if c != nil {
|
||||||
uplinkCounter = c
|
uplinkCounter = c
|
||||||
}
|
}
|
||||||
@@ -47,7 +44,7 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
|
|||||||
if len(tag) > 0 && policy.ForSystem().Stats.OutboundDownlink {
|
if len(tag) > 0 && policy.ForSystem().Stats.OutboundDownlink {
|
||||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||||
name := "outbound>>>" + tag + ">>>traffic>>>downlink"
|
name := "outbound>>>" + tag + ">>>traffic>>>downlink"
|
||||||
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
c, _ := statsManager.GetOrRegisterCounter(name)
|
||||||
if c != nil {
|
if c != nil {
|
||||||
downlinkCounter = c
|
downlinkCounter = c
|
||||||
}
|
}
|
||||||
@@ -63,7 +60,6 @@ type Handler struct {
|
|||||||
streamSettings *internet.MemoryStreamConfig
|
streamSettings *internet.MemoryStreamConfig
|
||||||
proxyConfig proto.Message
|
proxyConfig proto.Message
|
||||||
proxy proxy.Outbound
|
proxy proxy.Outbound
|
||||||
outboundManager outbound.Manager
|
|
||||||
mux *mux.ClientManager
|
mux *mux.ClientManager
|
||||||
xudp *mux.ClientManager
|
xudp *mux.ClientManager
|
||||||
udp443 string
|
udp443 string
|
||||||
@@ -77,7 +73,6 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
|
|||||||
uplinkCounter, downlinkCounter := getStatCounter(v, config.Tag)
|
uplinkCounter, downlinkCounter := getStatCounter(v, config.Tag)
|
||||||
h := &Handler{
|
h := &Handler{
|
||||||
tag: config.Tag,
|
tag: config.Tag,
|
||||||
outboundManager: v.GetFeature(outbound.ManagerType()).(outbound.Manager),
|
|
||||||
uplinkCounter: uplinkCounter,
|
uplinkCounter: uplinkCounter,
|
||||||
downlinkCounter: downlinkCounter,
|
downlinkCounter: downlinkCounter,
|
||||||
}
|
}
|
||||||
@@ -92,7 +87,7 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
|
|||||||
h.senderSettings = s
|
h.senderSettings = s
|
||||||
mss, err := internet.ToMemoryStreamConfig(s.StreamSettings)
|
mss, err := internet.ToMemoryStreamConfig(s.StreamSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to parse stream settings").Base(err).AtWarning()
|
return nil, errors.New("failed to parse stream settings").Base(err)
|
||||||
}
|
}
|
||||||
h.streamSettings = mss
|
h.streamSettings = mss
|
||||||
default:
|
default:
|
||||||
@@ -108,9 +103,11 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
|
|||||||
|
|
||||||
ctx = session.ContextWithFullHandler(ctx, h)
|
ctx = session.ContextWithFullHandler(ctx, h)
|
||||||
|
|
||||||
newCtx := session.ContextWithStreamSettings(ctx, h.streamSettings)
|
if h.streamSettings != nil {
|
||||||
|
ctx = session.ContextWithStreamSettings(ctx, h.streamSettings)
|
||||||
|
}
|
||||||
|
|
||||||
rawProxyHandler, err := common.CreateObject(newCtx, proxyConfig)
|
rawProxyHandler, err := common.CreateObject(ctx, proxyConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -197,7 +194,6 @@ func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) {
|
|||||||
common.Interrupt(link.Reader)
|
common.Interrupt(link.Reader)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
unchangedDomain := ob.Target.Address.Domain()
|
unchangedDomain := ob.Target.Address.Domain()
|
||||||
ob.Target.Address = net.IPAddress(ips[dice.Roll(len(ips))])
|
ob.Target.Address = net.IPAddress(ips[dice.Roll(len(ips))])
|
||||||
@@ -221,7 +217,7 @@ func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) {
|
|||||||
if ob.Target.Network == net.Network_UDP && ob.Target.Port == 443 {
|
if ob.Target.Network == net.Network_UDP && ob.Target.Port == 443 {
|
||||||
switch h.udp443 {
|
switch h.udp443 {
|
||||||
case "reject":
|
case "reject":
|
||||||
test(errors.New("XUDP rejected UDP/443 traffic").AtInfo())
|
test(errors.New("XUDP rejected UDP/443 traffic"))
|
||||||
return
|
return
|
||||||
case "skip":
|
case "skip":
|
||||||
goto out
|
goto out
|
||||||
@@ -270,66 +266,26 @@ func (h *Handler) DestIpAddress() net.IP {
|
|||||||
|
|
||||||
// Dial implements internet.Dialer.
|
// Dial implements internet.Dialer.
|
||||||
func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) {
|
func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) {
|
||||||
if h.senderSettings != nil {
|
if h.senderSettings != nil && h.senderSettings.Via != nil {
|
||||||
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
if h.senderSettings.ProxySettings.HasTag() {
|
ob := outbounds[len(outbounds)-1]
|
||||||
|
h.SetOutboundGateway(ctx, ob)
|
||||||
tag := h.senderSettings.ProxySettings.Tag
|
|
||||||
handler := h.outboundManager.GetHandler(tag)
|
|
||||||
if handler != nil {
|
|
||||||
errors.LogDebug(ctx, "proxying to ", tag, " for dest ", dest)
|
|
||||||
outbounds := session.OutboundsFromContext(ctx)
|
|
||||||
ctx = session.ContextWithOutbounds(ctx, append(outbounds, &session.Outbound{
|
|
||||||
Target: dest,
|
|
||||||
Tag: tag,
|
|
||||||
})) // add another outbound in session ctx
|
|
||||||
opts := pipe.OptionsFromContext(ctx)
|
|
||||||
uplinkReader, uplinkWriter := pipe.New(opts...)
|
|
||||||
downlinkReader, downlinkWriter := pipe.New(opts...)
|
|
||||||
|
|
||||||
go handler.Dispatch(ctx, &transport.Link{Reader: uplinkReader, Writer: downlinkWriter})
|
|
||||||
conn := cnc.NewConnection(cnc.ConnectionInputMulti(uplinkWriter), cnc.ConnectionOutputMulti(downlinkReader))
|
|
||||||
|
|
||||||
if config := tls.ConfigFromStreamSettings(h.streamSettings); config != nil {
|
|
||||||
tlsConfig := config.GetTLSConfig(tls.WithDestination(dest))
|
|
||||||
conn = tls.Client(conn, tlsConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
return h.getStatCouterConnection(conn), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
errors.LogError(ctx, "failed to get outbound handler with tag: ", tag)
|
|
||||||
return nil, errors.New("failed to get outbound handler with tag: " + tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
if h.senderSettings.Via != nil {
|
|
||||||
outbounds := session.OutboundsFromContext(ctx)
|
|
||||||
ob := outbounds[len(outbounds)-1]
|
|
||||||
h.SetOutboundGateway(ctx, ob)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := internet.Dial(ctx, dest, h.streamSettings)
|
conn, err := internet.Dial(ctx, dest, h.streamSettings)
|
||||||
conn = h.getStatCouterConnection(conn)
|
conn = h.getStatCouterConnection(conn)
|
||||||
outbounds := session.OutboundsFromContext(ctx)
|
|
||||||
if outbounds != nil {
|
|
||||||
ob := outbounds[len(outbounds)-1]
|
|
||||||
ob.Conn = conn
|
|
||||||
} else {
|
|
||||||
// for Vision's pre-connect
|
|
||||||
}
|
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound) {
|
func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound) {
|
||||||
if ob.Gateway == nil && h.senderSettings != nil && h.senderSettings.Via != nil && !h.senderSettings.ProxySettings.HasTag() && (h.streamSettings.SocketSettings == nil || len(h.streamSettings.SocketSettings.DialerProxy) == 0) {
|
if ob.Gateway == nil && h.senderSettings != nil && h.senderSettings.Via != nil &&
|
||||||
|
(h.streamSettings.SocketSettings == nil || len(h.streamSettings.SocketSettings.DialerProxy) == 0) {
|
||||||
var domain string
|
var domain string
|
||||||
addr := h.senderSettings.Via.AsAddress()
|
addr := h.senderSettings.Via.AsAddress()
|
||||||
domain = h.senderSettings.Via.GetDomain()
|
domain = h.senderSettings.Via.GetDomain()
|
||||||
switch {
|
switch {
|
||||||
case h.senderSettings.ViaCidr != "":
|
case h.senderSettings.ViaCidr != "":
|
||||||
ob.Gateway = ParseRandomIP(addr, h.senderSettings.ViaCidr)
|
ob.Gateway = ParseRandomIP(addr, h.senderSettings.ViaCidr)
|
||||||
|
|
||||||
case domain == "origin":
|
case domain == "origin":
|
||||||
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
||||||
if inbound.Local.IsValid() && inbound.Local.Address.Family().IsIP() {
|
if inbound.Local.IsValid() && inbound.Local.Address.Family().IsIP() {
|
||||||
@@ -344,12 +300,9 @@ func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound)
|
|||||||
errors.LogDebug(ctx, "use inbound source ip as sendthrough: ", inbound.Source.Address.String())
|
errors.LogDebug(ctx, "use inbound source ip as sendthrough: ", inbound.Source.Address.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// case addr.Family().IsDomain():
|
default: // case addr.Family().IsDomain():
|
||||||
default:
|
|
||||||
ob.Gateway = addr
|
ob.Gateway = addr
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,13 +68,13 @@ func (p *Portal) HandleConnection(ctx context.Context, link *transport.Link) err
|
|||||||
outbounds := session.OutboundsFromContext(ctx)
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
ob := outbounds[len(outbounds)-1]
|
ob := outbounds[len(outbounds)-1]
|
||||||
if ob == nil {
|
if ob == nil {
|
||||||
return errors.New("outbound metadata not found").AtError()
|
return errors.New("outbound metadata not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
if isDomain(ob.Target, p.domain) {
|
if isDomain(ob.Target, p.domain) {
|
||||||
muxClient, err := mux.NewClientWorker(*link, mux.ClientStrategy{})
|
muxClient, err := mux.NewClientWorker(*link, mux.ClientStrategy{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("failed to create mux client worker").Base(err).AtWarning()
|
return errors.New("failed to create mux client worker").Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
worker, err := NewPortalWorker(muxClient)
|
worker, err := NewPortalWorker(muxClient)
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ func (b *Balancer) SelectOutbounds() ([]string, error) {
|
|||||||
|
|
||||||
// GetPrincipleTarget implements routing.BalancerPrincipleTarget
|
// GetPrincipleTarget implements routing.BalancerPrincipleTarget
|
||||||
func (r *Router) GetPrincipleTarget(tag string) ([]string, error) {
|
func (r *Router) GetPrincipleTarget(tag string) ([]string, error) {
|
||||||
if b, ok := r.balancers[tag]; ok {
|
if b, ok := (*r.balancers.Load())[tag]; ok {
|
||||||
if s, ok := b.strategy.(BalancingPrincipleTarget); ok {
|
if s, ok := b.strategy.(BalancingPrincipleTarget); ok {
|
||||||
candidates, err := b.SelectOutbounds()
|
candidates, err := b.SelectOutbounds()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -151,7 +151,7 @@ func (r *Router) GetPrincipleTarget(tag string) ([]string, error) {
|
|||||||
|
|
||||||
// SetOverrideTarget implements routing.BalancerOverrider
|
// SetOverrideTarget implements routing.BalancerOverrider
|
||||||
func (r *Router) SetOverrideTarget(tag, target string) error {
|
func (r *Router) SetOverrideTarget(tag, target string) error {
|
||||||
if b, ok := r.balancers[tag]; ok {
|
if b, ok := (*r.balancers.Load())[tag]; ok {
|
||||||
b.override.Put(target)
|
b.override.Put(target)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -160,7 +160,7 @@ func (r *Router) SetOverrideTarget(tag, target string) error {
|
|||||||
|
|
||||||
// GetOverrideTarget implements routing.BalancerOverrider
|
// GetOverrideTarget implements routing.BalancerOverrider
|
||||||
func (r *Router) GetOverrideTarget(tag string) (string, error) {
|
func (r *Router) GetOverrideTarget(tag string) (string, error) {
|
||||||
if b, ok := r.balancers[tag]; ok {
|
if b, ok := (*r.balancers.Load())[tag]; ok {
|
||||||
return b.override.Get(), nil
|
return b.override.Get(), nil
|
||||||
}
|
}
|
||||||
return "", errors.New("cannot find tag")
|
return "", errors.New("cannot find tag")
|
||||||
|
|||||||
@@ -2,25 +2,8 @@ package router
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
sync "sync"
|
sync "sync"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *Router) OverrideBalancer(balancer string, target string) error {
|
|
||||||
var b *Balancer
|
|
||||||
for tag, bl := range r.balancers {
|
|
||||||
if tag == balancer {
|
|
||||||
b = bl
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if b == nil {
|
|
||||||
return errors.New("balancer '", balancer, "' not found")
|
|
||||||
}
|
|
||||||
b.override.Put(target)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type overrideSettings struct {
|
type overrideSettings struct {
|
||||||
target string
|
target string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"runtime"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -393,3 +394,22 @@ func (m *ProcessNameMatcher) Apply(ctx routing.Context) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LocalOSMatcher matches the operating system Xray itself is running on. That never
|
||||||
|
// changes while Xray is running, so the result is resolved when the rule is built.
|
||||||
|
type LocalOSMatcher struct {
|
||||||
|
matched bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLocalOSMatcher(names []string) *LocalOSMatcher {
|
||||||
|
return &LocalOSMatcher{
|
||||||
|
matched: slices.ContainsFunc(names, func(name string) bool {
|
||||||
|
return strings.EqualFold(name, runtime.GOOS)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply implements Condition.
|
||||||
|
func (m *LocalOSMatcher) Apply(_ routing.Context) bool {
|
||||||
|
return m.matched
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package router_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/xtls/xray-core/app/router"
|
. "github.com/xtls/xray-core/app/router"
|
||||||
@@ -343,6 +345,31 @@ func TestChinaSites(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLocalOSRule(t *testing.T) {
|
||||||
|
otherOS := "plan9"
|
||||||
|
if runtime.GOOS == otherOS {
|
||||||
|
otherOS = "linux"
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
localOS []string
|
||||||
|
output bool
|
||||||
|
}{
|
||||||
|
{localOS: []string{runtime.GOOS}, output: true},
|
||||||
|
{localOS: []string{otherOS}, output: false},
|
||||||
|
{localOS: []string{otherOS, runtime.GOOS}, output: true},
|
||||||
|
{localOS: []string{strings.ToUpper(runtime.GOOS)}, output: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range cases {
|
||||||
|
cond, err := (&RoutingRule{LocalOs: test.localOS}).BuildCondition()
|
||||||
|
common.Must(err)
|
||||||
|
if got := cond.Apply(withBackground()); got != test.output {
|
||||||
|
t.Errorf("for localOS %v on %s: expected %v, got %v", test.localOS, runtime.GOOS, test.output, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkMphDomainMatcher(b *testing.B) {
|
func BenchmarkMphDomainMatcher(b *testing.B) {
|
||||||
b.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
b.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||||
rules, err := geodata.ParseDomainRules([]string{"geosite:cn"}, geodata.Domain_Substr)
|
rules, err := geodata.ParseDomainRules([]string{"geosite:cn"}, geodata.Domain_Substr)
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ func (r *Rule) Apply(ctx routing.Context) bool {
|
|||||||
func (rr *RoutingRule) BuildCondition() (Condition, error) {
|
func (rr *RoutingRule) BuildCondition() (Condition, error) {
|
||||||
conds := NewConditionChan()
|
conds := NewConditionChan()
|
||||||
|
|
||||||
|
if len(rr.LocalOs) > 0 {
|
||||||
|
conds.Add(NewLocalOSMatcher(rr.LocalOs))
|
||||||
|
}
|
||||||
|
|
||||||
if len(rr.InboundTag) > 0 {
|
if len(rr.InboundTag) > 0 {
|
||||||
conds.Add(NewInboundTagMatcher(rr.InboundTag))
|
conds.Add(NewInboundTagMatcher(rr.InboundTag))
|
||||||
}
|
}
|
||||||
@@ -111,7 +115,7 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if conds.Len() == 0 {
|
if conds.Len() == 0 {
|
||||||
return nil, errors.New("this rule has no effective fields").AtWarning()
|
return nil, errors.New("this rule has no effective fields")
|
||||||
}
|
}
|
||||||
|
|
||||||
return conds, nil
|
return conds, nil
|
||||||
@@ -141,7 +145,7 @@ func (br *BalancingRule) Build(ohm outbound.Manager, dispatcher routing.Dispatch
|
|||||||
}
|
}
|
||||||
s, ok := i.(*StrategyLeastLoadConfig)
|
s, ok := i.(*StrategyLeastLoadConfig)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("not a StrategyLeastLoadConfig").AtError()
|
return nil, errors.New("not a StrategyLeastLoadConfig")
|
||||||
}
|
}
|
||||||
leastLoadStrategy := NewLeastLoadStrategy(s)
|
leastLoadStrategy := NewLeastLoadStrategy(s)
|
||||||
return &Balancer{
|
return &Balancer{
|
||||||
|
|||||||
+14
-4
@@ -107,8 +107,10 @@ type RoutingRule struct {
|
|||||||
VlessRouteList *net.PortList `protobuf:"bytes,20,opt,name=vless_route_list,json=vlessRouteList,proto3" json:"vless_route_list,omitempty"`
|
VlessRouteList *net.PortList `protobuf:"bytes,20,opt,name=vless_route_list,json=vlessRouteList,proto3" json:"vless_route_list,omitempty"`
|
||||||
Process []string `protobuf:"bytes,21,rep,name=process,proto3" json:"process,omitempty"`
|
Process []string `protobuf:"bytes,21,rep,name=process,proto3" json:"process,omitempty"`
|
||||||
Webhook *WebhookConfig `protobuf:"bytes,22,opt,name=webhook,proto3" json:"webhook,omitempty"`
|
Webhook *WebhookConfig `protobuf:"bytes,22,opt,name=webhook,proto3" json:"webhook,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
// List of operating systems for matching the one Xray itself is running on.
|
||||||
sizeCache protoimpl.SizeCache
|
LocalOs []string `protobuf:"bytes,23,rep,name=local_os,json=localOs,proto3" json:"local_os,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoutingRule) Reset() {
|
func (x *RoutingRule) Reset() {
|
||||||
@@ -278,6 +280,13 @@ func (x *RoutingRule) GetWebhook() *WebhookConfig {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *RoutingRule) GetLocalOs() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.LocalOs
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type isRoutingRule_TargetTag interface {
|
type isRoutingRule_TargetTag interface {
|
||||||
isRoutingRule_TargetTag()
|
isRoutingRule_TargetTag()
|
||||||
}
|
}
|
||||||
@@ -637,7 +646,7 @@ var File_app_router_config_proto protoreflect.FileDescriptor
|
|||||||
|
|
||||||
const file_app_router_config_proto_rawDesc = "" +
|
const file_app_router_config_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\x17app/router/config.proto\x12\x0fxray.app.router\x1a!common/serial/typed_message.proto\x1a\x15common/net/port.proto\x1a\x18common/net/network.proto\x1a\x1bcommon/geodata/geodat.proto\"\xc1\a\n" +
|
"\x17app/router/config.proto\x12\x0fxray.app.router\x1a!common/serial/typed_message.proto\x1a\x15common/net/port.proto\x1a\x18common/net/network.proto\x1a\x1bcommon/geodata/geodat.proto\"\xdc\a\n" +
|
||||||
"\vRoutingRule\x12\x12\n" +
|
"\vRoutingRule\x12\x12\n" +
|
||||||
"\x03tag\x18\x01 \x01(\tH\x00R\x03tag\x12%\n" +
|
"\x03tag\x18\x01 \x01(\tH\x00R\x03tag\x12%\n" +
|
||||||
"\rbalancing_tag\x18\f \x01(\tH\x00R\fbalancingTag\x12\x19\n" +
|
"\rbalancing_tag\x18\f \x01(\tH\x00R\fbalancingTag\x12\x19\n" +
|
||||||
@@ -661,7 +670,8 @@ const file_app_router_config_proto_rawDesc = "" +
|
|||||||
"\x0flocal_port_list\x18\x12 \x01(\v2\x19.xray.common.net.PortListR\rlocalPortList\x12C\n" +
|
"\x0flocal_port_list\x18\x12 \x01(\v2\x19.xray.common.net.PortListR\rlocalPortList\x12C\n" +
|
||||||
"\x10vless_route_list\x18\x14 \x01(\v2\x19.xray.common.net.PortListR\x0evlessRouteList\x12\x18\n" +
|
"\x10vless_route_list\x18\x14 \x01(\v2\x19.xray.common.net.PortListR\x0evlessRouteList\x12\x18\n" +
|
||||||
"\aprocess\x18\x15 \x03(\tR\aprocess\x128\n" +
|
"\aprocess\x18\x15 \x03(\tR\aprocess\x128\n" +
|
||||||
"\awebhook\x18\x16 \x01(\v2\x1e.xray.app.router.WebhookConfigR\awebhook\x1a=\n" +
|
"\awebhook\x18\x16 \x01(\v2\x1e.xray.app.router.WebhookConfigR\awebhook\x12\x19\n" +
|
||||||
|
"\blocal_os\x18\x17 \x03(\tR\alocalOs\x1a=\n" +
|
||||||
"\x0fAttributesEntry\x12\x10\n" +
|
"\x0fAttributesEntry\x12\x10\n" +
|
||||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\f\n" +
|
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\f\n" +
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ message RoutingRule {
|
|||||||
|
|
||||||
repeated string process = 21;
|
repeated string process = 21;
|
||||||
WebhookConfig webhook = 22;
|
WebhookConfig webhook = 22;
|
||||||
|
|
||||||
|
// List of operating systems for matching the one Xray itself is running on.
|
||||||
|
repeated string local_os = 23;
|
||||||
}
|
}
|
||||||
|
|
||||||
message WebhookConfig {
|
message WebhookConfig {
|
||||||
|
|||||||
+59
-114
@@ -2,7 +2,9 @@ package router
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"maps"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
@@ -17,8 +19,8 @@ import (
|
|||||||
// Router is an implementation of routing.Router.
|
// Router is an implementation of routing.Router.
|
||||||
type Router struct {
|
type Router struct {
|
||||||
domainStrategy Config_DomainStrategy
|
domainStrategy Config_DomainStrategy
|
||||||
rules []*Rule
|
rules atomic.Pointer[[]*Rule]
|
||||||
balancers map[string]*Balancer
|
balancers atomic.Pointer[map[string]*Balancer]
|
||||||
dns dns.Client
|
dns dns.Client
|
||||||
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -43,52 +45,9 @@ func (r *Router) Init(ctx context.Context, config *Config, d dns.Client, ohm out
|
|||||||
r.ohm = ohm
|
r.ohm = ohm
|
||||||
r.dispatcher = dispatcher
|
r.dispatcher = dispatcher
|
||||||
|
|
||||||
r.balancers = make(map[string]*Balancer, len(config.BalancingRule))
|
r.rules.Store(new([]*Rule))
|
||||||
for _, rule := range config.BalancingRule {
|
r.balancers.Store(&map[string]*Balancer{})
|
||||||
balancer, err := rule.Build(ohm, dispatcher)
|
return r.ReloadRules(config, false)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
balancer.InjectContext(ctx)
|
|
||||||
r.balancers[rule.Tag] = balancer
|
|
||||||
}
|
|
||||||
|
|
||||||
r.rules = make([]*Rule, 0, len(config.Rule))
|
|
||||||
for _, rule := range config.Rule {
|
|
||||||
cond, err := rule.BuildCondition()
|
|
||||||
if err != nil {
|
|
||||||
r.closeWebhooks()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rr := &Rule{
|
|
||||||
Condition: cond,
|
|
||||||
Tag: rule.GetTag(),
|
|
||||||
RuleTag: rule.GetRuleTag(),
|
|
||||||
}
|
|
||||||
if wh := rule.GetWebhook(); wh != nil {
|
|
||||||
notifier, err := NewWebhookNotifier(wh)
|
|
||||||
if err != nil {
|
|
||||||
r.closeWebhooks()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rr.Webhook = notifier
|
|
||||||
}
|
|
||||||
btag := rule.GetBalancingTag()
|
|
||||||
if len(btag) > 0 {
|
|
||||||
brule, found := r.balancers[btag]
|
|
||||||
if !found {
|
|
||||||
if rr.Webhook != nil {
|
|
||||||
rr.Webhook.Close()
|
|
||||||
}
|
|
||||||
r.closeWebhooks()
|
|
||||||
return errors.New("balancer ", btag, " not found")
|
|
||||||
}
|
|
||||||
rr.Balancer = brule
|
|
||||||
}
|
|
||||||
r.rules = append(r.rules, rr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PickRoute implements routing.Router.
|
// PickRoute implements routing.Router.
|
||||||
@@ -124,18 +83,22 @@ func (r *Router) ReloadRules(config *Config, shouldAppend bool) error {
|
|||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
if !shouldAppend {
|
oldRules := *r.rules.Load()
|
||||||
for _, rule := range r.rules {
|
oldBalancers := *r.balancers.Load()
|
||||||
if rule.Webhook != nil {
|
|
||||||
rule.Webhook.Close()
|
var newRules []*Rule
|
||||||
}
|
newBalancers := make(map[string]*Balancer)
|
||||||
|
existTags := make(map[string]bool, len(oldRules)+len(config.Rule))
|
||||||
|
if shouldAppend {
|
||||||
|
newRules = append(newRules, oldRules...)
|
||||||
|
maps.Copy(newBalancers, oldBalancers)
|
||||||
|
for _, rule := range oldRules {
|
||||||
|
existTags[rule.RuleTag] = true
|
||||||
}
|
}
|
||||||
r.balancers = make(map[string]*Balancer, len(config.BalancingRule))
|
|
||||||
r.rules = make([]*Rule, 0, len(config.Rule))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rule := range config.BalancingRule {
|
for _, rule := range config.BalancingRule {
|
||||||
_, found := r.balancers[rule.Tag]
|
if _, found := newBalancers[rule.Tag]; found {
|
||||||
if found {
|
|
||||||
return errors.New("duplicate balancer tag")
|
return errors.New("duplicate balancer tag")
|
||||||
}
|
}
|
||||||
balancer, err := rule.Build(r.ohm, r.dispatcher)
|
balancer, err := rule.Build(r.ohm, r.dispatcher)
|
||||||
@@ -143,27 +106,12 @@ func (r *Router) ReloadRules(config *Config, shouldAppend bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
balancer.InjectContext(r.ctx)
|
balancer.InjectContext(r.ctx)
|
||||||
r.balancers[rule.Tag] = balancer
|
newBalancers[rule.Tag] = balancer
|
||||||
}
|
|
||||||
|
|
||||||
startIdx := len(r.rules)
|
|
||||||
closeNewWebhooks := func() {
|
|
||||||
for i := startIdx; i < len(r.rules); i++ {
|
|
||||||
if r.rules[i].Webhook != nil {
|
|
||||||
r.rules[i].Webhook.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r.rules = r.rules[:startIdx]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rule := range config.Rule {
|
for _, rule := range config.Rule {
|
||||||
if r.RuleExists(rule.GetRuleTag()) {
|
|
||||||
closeNewWebhooks()
|
|
||||||
return errors.New("duplicate ruleTag ", rule.GetRuleTag())
|
|
||||||
}
|
|
||||||
cond, err := rule.BuildCondition()
|
cond, err := rule.BuildCondition()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
closeNewWebhooks()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rr := &Rule{
|
rr := &Rule{
|
||||||
@@ -171,69 +119,64 @@ func (r *Router) ReloadRules(config *Config, shouldAppend bool) error {
|
|||||||
Tag: rule.GetTag(),
|
Tag: rule.GetTag(),
|
||||||
RuleTag: rule.GetRuleTag(),
|
RuleTag: rule.GetRuleTag(),
|
||||||
}
|
}
|
||||||
|
if rr.RuleTag != "" && existTags[rr.RuleTag] {
|
||||||
|
return errors.New("duplicate ruleTag ", rr.RuleTag)
|
||||||
|
}
|
||||||
|
existTags[rr.RuleTag] = true
|
||||||
if wh := rule.GetWebhook(); wh != nil {
|
if wh := rule.GetWebhook(); wh != nil {
|
||||||
notifier, err := NewWebhookNotifier(wh)
|
notifier, err := NewWebhookNotifier(wh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
closeNewWebhooks()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rr.Webhook = notifier
|
rr.Webhook = notifier
|
||||||
}
|
}
|
||||||
btag := rule.GetBalancingTag()
|
if btag := rule.GetBalancingTag(); len(btag) > 0 {
|
||||||
if len(btag) > 0 {
|
brule, found := newBalancers[btag]
|
||||||
brule, found := r.balancers[btag]
|
|
||||||
if !found {
|
if !found {
|
||||||
if rr.Webhook != nil {
|
|
||||||
rr.Webhook.Close()
|
|
||||||
}
|
|
||||||
closeNewWebhooks()
|
|
||||||
return errors.New("balancer ", btag, " not found")
|
return errors.New("balancer ", btag, " not found")
|
||||||
}
|
}
|
||||||
rr.Balancer = brule
|
rr.Balancer = brule
|
||||||
}
|
}
|
||||||
r.rules = append(r.rules, rr)
|
newRules = append(newRules, rr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.balancers.Store(&newBalancers)
|
||||||
|
r.rules.Store(&newRules)
|
||||||
|
if !shouldAppend {
|
||||||
|
closeWebhooks(oldRules)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) RuleExists(tag string) bool {
|
|
||||||
if tag != "" {
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
if rule.RuleTag == tag {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveRule implements routing.Router.
|
// RemoveRule implements routing.Router.
|
||||||
func (r *Router) RemoveRule(tag string) error {
|
func (r *Router) RemoveRule(tag string) error {
|
||||||
|
if tag == "" {
|
||||||
|
return errors.New("empty tag name!")
|
||||||
|
}
|
||||||
|
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
newRules := []*Rule{}
|
oldRules := *r.rules.Load()
|
||||||
if tag != "" {
|
newRules := make([]*Rule, 0, len(oldRules))
|
||||||
for _, rule := range r.rules {
|
var removed []*Rule
|
||||||
if rule.RuleTag != tag {
|
for _, rule := range oldRules {
|
||||||
newRules = append(newRules, rule)
|
if rule.RuleTag != tag {
|
||||||
} else if rule.Webhook != nil {
|
newRules = append(newRules, rule)
|
||||||
rule.Webhook.Close()
|
} else {
|
||||||
}
|
removed = append(removed, rule)
|
||||||
}
|
}
|
||||||
r.rules = newRules
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
return errors.New("empty tag name!")
|
r.rules.Store(&newRules)
|
||||||
|
closeWebhooks(removed)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListRule implements routing.Router
|
// ListRule implements routing.Router
|
||||||
func (r *Router) ListRule() []routing.Route {
|
func (r *Router) ListRule() []routing.Route {
|
||||||
r.mu.Lock()
|
rules := *r.rules.Load()
|
||||||
defer r.mu.Unlock()
|
ruleList := make([]routing.Route, 0, len(rules))
|
||||||
ruleList := make([]routing.Route, 0)
|
for _, rule := range rules {
|
||||||
for _, rule := range r.rules {
|
|
||||||
ruleList = append(ruleList, &Route{
|
ruleList = append(ruleList, &Route{
|
||||||
outboundTag: rule.Tag,
|
outboundTag: rule.Tag,
|
||||||
ruleTag: rule.RuleTag,
|
ruleTag: rule.RuleTag,
|
||||||
@@ -252,7 +195,9 @@ func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context,
|
|||||||
ctx = routing_dns.ContextWithDNSClient(ctx, r.dns)
|
ctx = routing_dns.ContextWithDNSClient(ctx, r.dns)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rule := range r.rules {
|
rules := *r.rules.Load()
|
||||||
|
|
||||||
|
for _, rule := range rules {
|
||||||
if rule.Apply(ctx) {
|
if rule.Apply(ctx) {
|
||||||
return rule, ctx, nil
|
return rule, ctx, nil
|
||||||
}
|
}
|
||||||
@@ -265,7 +210,7 @@ func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context,
|
|||||||
ctx = routing_dns.ContextWithDNSClient(ctx, r.dns)
|
ctx = routing_dns.ContextWithDNSClient(ctx, r.dns)
|
||||||
|
|
||||||
// Try applying rules again if we have IPs.
|
// Try applying rules again if we have IPs.
|
||||||
for _, rule := range r.rules {
|
for _, rule := range rules {
|
||||||
if rule.Apply(ctx) {
|
if rule.Apply(ctx) {
|
||||||
return rule, ctx, nil
|
return rule, ctx, nil
|
||||||
}
|
}
|
||||||
@@ -279,9 +224,9 @@ func (r *Router) Start() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// closeWebhooks closes all webhook notifiers in the current rule set.
|
// closeWebhooks closes all webhook notifiers in the given rule set.
|
||||||
func (r *Router) closeWebhooks() {
|
func closeWebhooks(rules []*Rule) {
|
||||||
for _, rule := range r.rules {
|
for _, rule := range rules {
|
||||||
if rule.Webhook != nil {
|
if rule.Webhook != nil {
|
||||||
rule.Webhook.Close()
|
rule.Webhook.Close()
|
||||||
}
|
}
|
||||||
@@ -292,7 +237,7 @@ func (r *Router) closeWebhooks() {
|
|||||||
func (r *Router) Close() error {
|
func (r *Router) Close() error {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
r.closeWebhooks()
|
closeWebhooks(*r.rules.Load())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-23
@@ -8,6 +8,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
@@ -40,6 +41,7 @@ type WebhookNotifier struct {
|
|||||||
deduplication uint32
|
deduplication uint32
|
||||||
client *http.Client
|
client *http.Client
|
||||||
seen sync.Map
|
seen sync.Map
|
||||||
|
lastSweep atomic.Int64
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
closeOnce sync.Once
|
closeOnce sync.Once
|
||||||
@@ -77,11 +79,6 @@ func NewWebhookNotifier(cfg *WebhookConfig) (*WebhookNotifier, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.deduplication > 0 {
|
|
||||||
h.wg.Add(1)
|
|
||||||
go h.cleanupLoop()
|
|
||||||
}
|
|
||||||
|
|
||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,6 +198,7 @@ func (h *WebhookNotifier) isDuplicate(email string) bool {
|
|||||||
}
|
}
|
||||||
ttl := time.Duration(h.deduplication) * time.Second
|
ttl := time.Duration(h.deduplication) * time.Second
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
h.maybeSweep(now, ttl)
|
||||||
if v, loaded := h.seen.LoadOrStore(email, now); loaded {
|
if v, loaded := h.seen.LoadOrStore(email, now); loaded {
|
||||||
if now.Sub(v.(time.Time)) < ttl {
|
if now.Sub(v.(time.Time)) < ttl {
|
||||||
return true
|
return true
|
||||||
@@ -210,27 +208,23 @@ func (h *WebhookNotifier) isDuplicate(email string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *WebhookNotifier) cleanupLoop() {
|
func (h *WebhookNotifier) maybeSweep(now time.Time, ttl time.Duration) {
|
||||||
defer h.wg.Done()
|
last := h.lastSweep.Load()
|
||||||
ttl := time.Duration(h.deduplication) * time.Second
|
if now.UnixNano()-last < int64(ttl) {
|
||||||
ticker := time.NewTicker(ttl)
|
return
|
||||||
defer ticker.Stop()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-h.done:
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
now := time.Now()
|
|
||||||
h.seen.Range(func(key, value any) bool {
|
|
||||||
if now.Sub(value.(time.Time)) >= ttl {
|
|
||||||
h.seen.Delete(key)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if !h.lastSweep.CompareAndSwap(last, now.UnixNano()) {
|
||||||
|
return // another goroutine did the sweep
|
||||||
|
}
|
||||||
|
h.seen.Range(func(key, value any) bool {
|
||||||
|
if now.Sub(value.(time.Time)) >= ttl {
|
||||||
|
h.seen.Delete(key)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only need to call if the Notifier is really used, otherwise GC can clean it
|
||||||
func (h *WebhookNotifier) Close() error {
|
func (h *WebhookNotifier) Close() error {
|
||||||
h.closeOnce.Do(func() {
|
h.closeOnce.Do(func() {
|
||||||
close(h.done)
|
close(h.done)
|
||||||
|
|||||||
@@ -48,6 +48,20 @@ func (m *Manager) RegisterCounter(name string) (stats.Counter, error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetOrRegisterCounter implements stats.Manager.
|
||||||
|
func (m *Manager) GetOrRegisterCounter(name string) (stats.Counter, error) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
|
||||||
|
if c, found := m.counters[name]; found {
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
errors.LogDebug(context.Background(), "create new counter ", name)
|
||||||
|
c := new(Counter)
|
||||||
|
m.counters[name] = c
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
// UnregisterCounter implements stats.Manager.
|
// UnregisterCounter implements stats.Manager.
|
||||||
func (m *Manager) UnregisterCounter(name string) error {
|
func (m *Manager) UnregisterCounter(name string) error {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
@@ -97,6 +111,20 @@ func (m *Manager) RegisterOnlineMap(name string) (stats.OnlineMap, error) {
|
|||||||
return om, nil
|
return om, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetOrRegisterOnlineMap implements stats.Manager.
|
||||||
|
func (m *Manager) GetOrRegisterOnlineMap(name string) (stats.OnlineMap, error) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
|
||||||
|
if om, found := m.onlineMaps[name]; found {
|
||||||
|
return om, nil
|
||||||
|
}
|
||||||
|
errors.LogDebug(context.Background(), "create new OnlineMap ", name)
|
||||||
|
om := NewOnlineMap()
|
||||||
|
m.onlineMaps[name] = om
|
||||||
|
return om, nil
|
||||||
|
}
|
||||||
|
|
||||||
// UnregisterOnlineMap implements stats.Manager.
|
// UnregisterOnlineMap implements stats.Manager.
|
||||||
func (m *Manager) UnregisterOnlineMap(name string) error {
|
func (m *Manager) UnregisterOnlineMap(name string) error {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
@@ -149,6 +177,26 @@ func (m *Manager) RegisterChannel(name string) (stats.Channel, error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetOrRegisterChannel implements stats.Manager.
|
||||||
|
func (m *Manager) GetOrRegisterChannel(name string) (stats.Channel, error) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
|
||||||
|
if c, found := m.channels[name]; found {
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
errors.LogDebug(context.Background(), "create new channel ", name)
|
||||||
|
c := NewChannel(&ChannelConfig{BufferSize: 64, Blocking: false})
|
||||||
|
if m.running {
|
||||||
|
// Start before publishing so no goroutine can observe an unstarted channel.
|
||||||
|
if err := c.Start(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.channels[name] = c
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
// UnregisterChannel implements stats.Manager.
|
// UnregisterChannel implements stats.Manager.
|
||||||
func (m *Manager) UnregisterChannel(name string) error {
|
func (m *Manager) UnregisterChannel(name string) error {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
|
|||||||
+1
-1
@@ -122,7 +122,7 @@ func NewReader(reader io.Reader) Reader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, isFile := reader.(*os.File)
|
_, isFile := reader.(*os.File)
|
||||||
if !isFile && useReadv {
|
if !isFile && useReadV() {
|
||||||
if sc, ok := reader.(syscall.Conn); ok {
|
if sc, ok := reader.(syscall.Conn); ok {
|
||||||
rawConn, err := sc.SyscallConn()
|
rawConn, err := sc.SyscallConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package buf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/platform"
|
"github.com/xtls/xray-core/common/platform"
|
||||||
@@ -143,13 +144,24 @@ func (r *ReadVReader) ReadMultiBuffer() (MultiBuffer, error) {
|
|||||||
return mb, nil
|
return mb, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var useReadv bool
|
var useReadv atomic.Bool
|
||||||
|
|
||||||
func init() {
|
func useReadV() bool {
|
||||||
|
return useReadv.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
func reloadEnvSettings() error {
|
||||||
const defaultFlagValue = "NOT_DEFINED_AT_ALL"
|
const defaultFlagValue = "NOT_DEFINED_AT_ALL"
|
||||||
value := platform.NewEnvFlag(platform.UseReadV).GetValue(func() string { return defaultFlagValue })
|
value := platform.NewEnvFlag(platform.UseReadV).GetValue(func() string { return defaultFlagValue })
|
||||||
|
enabled := false
|
||||||
switch value {
|
switch value {
|
||||||
case defaultFlagValue, "auto", "enable":
|
case defaultFlagValue, "auto", "enable":
|
||||||
useReadv = true
|
enabled = true
|
||||||
}
|
}
|
||||||
|
useReadv.Store(enabled)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
platform.RegisterEnvReload(reloadEnvSettings)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import (
|
|||||||
"github.com/xtls/xray-core/features/stats"
|
"github.com/xtls/xray-core/features/stats"
|
||||||
)
|
)
|
||||||
|
|
||||||
const useReadv = false
|
func useReadV() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func NewReadVReader(reader io.Reader, rawConn syscall.RawConn, counter stats.Counter) Reader {
|
func NewReadVReader(reader io.Reader, rawConn syscall.RawConn, counter stats.Counter) Reader {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type windowsReader struct {
|
type windowsReader struct {
|
||||||
bufs []syscall.WSABuf
|
bufs []syscall.WSABuf
|
||||||
|
ready bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *windowsReader) Init(bs []*Buffer) {
|
func (r *windowsReader) Init(bs []*Buffer) {
|
||||||
@@ -15,6 +16,7 @@ func (r *windowsReader) Init(bs []*Buffer) {
|
|||||||
for _, b := range bs {
|
for _, b := range bs {
|
||||||
r.bufs = append(r.bufs, syscall.WSABuf{Len: uint32(Size), Buf: &b.v[0]})
|
r.bufs = append(r.bufs, syscall.WSABuf{Len: uint32(Size), Buf: &b.v[0]})
|
||||||
}
|
}
|
||||||
|
r.ready = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *windowsReader) Clear() {
|
func (r *windowsReader) Clear() {
|
||||||
@@ -25,6 +27,14 @@ func (r *windowsReader) Clear() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *windowsReader) Read(fd uintptr) int32 {
|
func (r *windowsReader) Read(fd uintptr) int32 {
|
||||||
|
// On the first invocation, we return -1 to indicate "not ready"
|
||||||
|
// to make rawConn.Read wait for readability using the runtime's own mechanism
|
||||||
|
// because syscall.WSARecv() is a blocking call when used with nil OVERLAPPED
|
||||||
|
if !r.ready {
|
||||||
|
r.ready = true
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
var nBytes uint32
|
var nBytes uint32
|
||||||
var flags uint32
|
var flags uint32
|
||||||
err := syscall.WSARecv(syscall.Handle(fd), &r.bufs[0], uint32(len(r.bufs)), &nBytes, &flags, nil, nil)
|
err := syscall.WSARecv(syscall.Handle(fd), &r.bufs[0], uint32(len(r.bufs)), &nBytes, &flags, nil, nil)
|
||||||
|
|||||||
@@ -118,7 +118,9 @@ func (w *BufferedWriter) Write(b []byte) (int, error) {
|
|||||||
|
|
||||||
nBytes, err := w.buffer.Write(b)
|
nBytes, err := w.buffer.Write(b)
|
||||||
totalBytes += nBytes
|
totalBytes += nBytes
|
||||||
if err != nil {
|
|
||||||
|
// ErrBufferFull means a partial write, so flush below and continue
|
||||||
|
if err != nil && err != ErrBufferFull {
|
||||||
return totalBytes, err
|
return totalBytes, err
|
||||||
}
|
}
|
||||||
if !w.buffered || w.buffer.IsFull() {
|
if !w.buffered || w.buffer.IsFull() {
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ import (
|
|||||||
|
|
||||||
// [,)
|
// [,)
|
||||||
func RandBetween(from int64, to int64) int64 {
|
func RandBetween(from int64, to int64) int64 {
|
||||||
if from == to {
|
|
||||||
return from
|
|
||||||
}
|
|
||||||
if from > to {
|
if from > to {
|
||||||
from, to = to, from
|
from, to = to, from
|
||||||
}
|
}
|
||||||
|
if d := to - from; d == 0 || d == 1 {
|
||||||
|
return from
|
||||||
|
}
|
||||||
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
|
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
|
||||||
return from + bigInt.Int64()
|
return from + bigInt.Int64()
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-65
@@ -18,17 +18,12 @@ type hasInnerError interface {
|
|||||||
Unwrap() error
|
Unwrap() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type hasSeverity interface {
|
|
||||||
Severity() log.Severity
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error is an error object with underlying error.
|
// Error is an error object with underlying error.
|
||||||
type Error struct {
|
type Error struct {
|
||||||
prefix []interface{}
|
prefix []interface{}
|
||||||
message []interface{}
|
message []interface{}
|
||||||
caller string
|
caller string
|
||||||
inner error
|
inner error
|
||||||
severity log.Severity
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error implements error.Error().
|
// Error implements error.Error().
|
||||||
@@ -69,46 +64,6 @@ func (err *Error) Base(e error) *Error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (err *Error) atSeverity(s log.Severity) *Error {
|
|
||||||
err.severity = s
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err *Error) Severity() log.Severity {
|
|
||||||
if err.inner == nil {
|
|
||||||
return err.severity
|
|
||||||
}
|
|
||||||
|
|
||||||
if s, ok := err.inner.(hasSeverity); ok {
|
|
||||||
as := s.Severity()
|
|
||||||
if as < err.severity {
|
|
||||||
return as
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err.severity
|
|
||||||
}
|
|
||||||
|
|
||||||
// AtDebug sets the severity to debug.
|
|
||||||
func (err *Error) AtDebug() *Error {
|
|
||||||
return err.atSeverity(log.Severity_Debug)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AtInfo sets the severity to info.
|
|
||||||
func (err *Error) AtInfo() *Error {
|
|
||||||
return err.atSeverity(log.Severity_Info)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AtWarning sets the severity to warning.
|
|
||||||
func (err *Error) AtWarning() *Error {
|
|
||||||
return err.atSeverity(log.Severity_Warning)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AtError sets the severity to error.
|
|
||||||
func (err *Error) AtError() *Error {
|
|
||||||
return err.atSeverity(log.Severity_Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the string representation of this error.
|
// String returns the string representation of this error.
|
||||||
func (err *Error) String() string {
|
func (err *Error) String() string {
|
||||||
return err.Error()
|
return err.Error()
|
||||||
@@ -132,9 +87,8 @@ func New(msg ...interface{}) *Error {
|
|||||||
details = details[:i]
|
details = details[:i]
|
||||||
}
|
}
|
||||||
return &Error{
|
return &Error{
|
||||||
message: msg,
|
message: msg,
|
||||||
severity: log.Severity_Info,
|
caller: details,
|
||||||
caller: details,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,6 +125,9 @@ func LogErrorInner(ctx context.Context, inner error, msg ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func doLog(ctx context.Context, inner error, severity log.Severity, msg ...interface{}) {
|
func doLog(ctx context.Context, inner error, severity log.Severity, msg ...interface{}) {
|
||||||
|
if log.GetSeverity() < severity {
|
||||||
|
return
|
||||||
|
}
|
||||||
pc, _, _, _ := runtime.Caller(2)
|
pc, _, _, _ := runtime.Caller(2)
|
||||||
details := runtime.FuncForPC(pc).Name()
|
details := runtime.FuncForPC(pc).Name()
|
||||||
if len(details) >= trim {
|
if len(details) >= trim {
|
||||||
@@ -181,10 +138,9 @@ func doLog(ctx context.Context, inner error, severity log.Severity, msg ...inter
|
|||||||
details = details[:i]
|
details = details[:i]
|
||||||
}
|
}
|
||||||
err := &Error{
|
err := &Error{
|
||||||
message: msg,
|
message: msg,
|
||||||
severity: severity,
|
caller: details,
|
||||||
caller: details,
|
inner: inner,
|
||||||
inner: inner,
|
|
||||||
}
|
}
|
||||||
if ctx != nil && ctx != context.Background() {
|
if ctx != nil && ctx != context.Background() {
|
||||||
id := uint32(c.IDFromContext(ctx))
|
id := uint32(c.IDFromContext(ctx))
|
||||||
@@ -193,7 +149,7 @@ func doLog(ctx context.Context, inner error, severity log.Severity, msg ...inter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Record(&log.GeneralMessage{
|
log.Record(&log.GeneralMessage{
|
||||||
Severity: GetSeverity(err),
|
Severity: severity,
|
||||||
Content: err,
|
Content: err,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -217,11 +173,3 @@ L:
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSeverity returns the actual severity of the error, including inner errors.
|
|
||||||
func GetSeverity(err error) log.Severity {
|
|
||||||
if s, ok := err.(hasSeverity); ok {
|
|
||||||
return s.Severity()
|
|
||||||
}
|
|
||||||
return log.Severity_Info
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,30 +7,21 @@ import (
|
|||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
. "github.com/xtls/xray-core/common/errors"
|
. "github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestError(t *testing.T) {
|
func TestError(t *testing.T) {
|
||||||
err := New("TestError")
|
err := New("TestError")
|
||||||
if v := GetSeverity(err); v != log.Severity_Info {
|
if v := err.Error(); !strings.Contains(v, "TestError") {
|
||||||
t.Error("severity: ", v)
|
t.Error("error: ", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = New("TestError2").Base(io.EOF)
|
err = New("TestError2").Base(io.EOF)
|
||||||
if v := GetSeverity(err); v != log.Severity_Info {
|
if v := err.Error(); !strings.Contains(v, "EOF") {
|
||||||
t.Error("severity: ", v)
|
t.Error("error: ", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = New("TestError3").Base(io.EOF).AtWarning()
|
err = New("TestError3").Base(io.EOF)
|
||||||
if v := GetSeverity(err); v != log.Severity_Warning {
|
err = New("TestError4").Base(err)
|
||||||
t.Error("severity: ", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = New("TestError4").Base(io.EOF).AtWarning()
|
|
||||||
err = New("TestError5").Base(err)
|
|
||||||
if v := GetSeverity(err); v != log.Severity_Warning {
|
|
||||||
t.Error("severity: ", v)
|
|
||||||
}
|
|
||||||
if v := err.Error(); !strings.Contains(v, "EOF") {
|
if v := err.Error(); !strings.Contains(v, "EOF") {
|
||||||
t.Error("error: ", v)
|
t.Error("error: ", v)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package strmatcher_test
|
package strmatcher_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -72,6 +73,64 @@ func BenchmarkSubstrMatcher(b *testing.B) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkRegexMatcher(b *testing.B) {
|
||||||
|
patterns := []string{ // taken from geosite
|
||||||
|
`(^|\.)91porn\.(best|com|cool|fun|group|party|plus|site|tw|work)$`,
|
||||||
|
`(^|\.)91porn[0-9]{3}\.me$`,
|
||||||
|
`(^|\.)apiproxy-device-prod-nlb-.+\.amazonaws\.com$`,
|
||||||
|
`(^|\.)dualstack\.apiproxy-.+\.amazonaws\.com$`,
|
||||||
|
`(^|\.)aqdk[0-9]{3}\.com$`,
|
||||||
|
`(^|\.)bilibili3(0[1-9]|1[0-2])\.xyz$`,
|
||||||
|
`(^|\.)byyum([3589]|2[235689]|3[34]|4[1-9]|5[1-79]|6[0134679])?\.com$`,
|
||||||
|
`(^|\.)fiftymvapi\..+$`,
|
||||||
|
`(^|\.)gossipfuli[0-9]{3,4}\.xyz$`,
|
||||||
|
`(^|\.)kpkuang\.(bond|fun|info|one|us)$`,
|
||||||
|
`(^|\.)rule34\.(asia|us|world|xxx|xyz)$`,
|
||||||
|
`(^|\.)[a-z][1-9][0-9][a-z]\.com$`,
|
||||||
|
`.+\.awsdns-[0-9][0-9]\.(co\.uk|com|net|org)$`,
|
||||||
|
`.+\.dkr\.ecr\.[^\.]+\.amazonaws\.com$`,
|
||||||
|
`^(.+\.)*zh\.okaapps\.com$`,
|
||||||
|
`^cdn\d-epicgames-\d+\.file\.myqcloud\.com$`,
|
||||||
|
`^chatgpt-async-webps-prod-\S+-\d+\.webpubsub\.azure\.com$`,
|
||||||
|
`^r+[0-9]+(---|\.)sn-(2x3|ni5|j5o)\w{5}\.googlevideo\.com$`,
|
||||||
|
`^speed\.(coe|open)\.ad\.[a-z]{2,6}\.prod\.hosts\.ooklaserver\.net$`,
|
||||||
|
`javdb\d+\.com$`,
|
||||||
|
}
|
||||||
|
domains := []string{
|
||||||
|
"www.google.com", "rr3---sn-4g5edndy.googlevideo.com", "r1---sn-2x3abcde.googlevideo.com", "i.ytimg.com",
|
||||||
|
"graph.facebook.com", "api.twitter.com", "www.baidu.com", "github.com", "objects.githubusercontent.com",
|
||||||
|
"login.microsoftonline.com", "e1234.dscb.akamaiedge.net", "d1a2b3c4d5e6f7.cloudfront.net",
|
||||||
|
"s3.us-east-1.amazonaws.com", "123456789012.dkr.ecr.us-east-1.amazonaws.com", "www.wikipedia.org",
|
||||||
|
"discord.com", "telegram.org", "store.steampowered.com", "www.91porn.com", "ns-1234.awsdns-12.org",
|
||||||
|
}
|
||||||
|
bench := func(b *testing.B, ctor func(pattern string) func(string) bool) {
|
||||||
|
var matchers []func(string) bool
|
||||||
|
for _, p := range patterns {
|
||||||
|
matchers = append(matchers, ctor(p))
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for _, d := range domains {
|
||||||
|
for _, match := range matchers {
|
||||||
|
_ = match(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.Run("regexp", func(b *testing.B) {
|
||||||
|
bench(b, func(pattern string) func(string) bool {
|
||||||
|
return regexp.MustCompile(pattern).MatchString
|
||||||
|
})
|
||||||
|
})
|
||||||
|
b.Run("prefilter", func(b *testing.B) {
|
||||||
|
bench(b, func(pattern string) func(string) bool {
|
||||||
|
m, err := Regex.New(pattern)
|
||||||
|
common.Must(err)
|
||||||
|
return m.Match
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Utility functions for benchmark
|
// Utility functions for benchmark
|
||||||
|
|
||||||
func benchmarkMatcherType(b *testing.B, t Type, ctor func() MatcherGroup) {
|
func benchmarkMatcherType(b *testing.B, t Type, ctor func() MatcherGroup) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package strmatcher
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"regexp/syntax"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@@ -73,7 +74,43 @@ func (m SubstrMatcher) Match(s string) bool {
|
|||||||
|
|
||||||
// RegexMatcher is an implementation of Matcher.
|
// RegexMatcher is an implementation of Matcher.
|
||||||
type RegexMatcher struct {
|
type RegexMatcher struct {
|
||||||
pattern *regexp.Regexp
|
pattern *regexp.Regexp
|
||||||
|
literals []string // every match contains all of them, longest first
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRegexMatcher(pattern string) (Matcher, error) {
|
||||||
|
regex, err := regexp.Compile(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m := &RegexMatcher{pattern: regex}
|
||||||
|
if re, err := syntax.Parse(pattern, syntax.Perl); err == nil { // same flags as regexp.Compile
|
||||||
|
m.literals = requiredLiterals(re, nil)
|
||||||
|
slices.SortStableFunc(m.literals, func(a, b string) int { return len(b) - len(a) })
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// requiredLiterals appends to dst the case-sensitive strings that every match of re contains.
|
||||||
|
func requiredLiterals(re *syntax.Regexp, dst []string) []string {
|
||||||
|
switch re.Op {
|
||||||
|
case syntax.OpLiteral:
|
||||||
|
// regexp matches U+FFFD against invalid UTF-8 bytes, strings.Contains does not
|
||||||
|
if re.Flags&syntax.FoldCase == 0 && !slices.Contains(re.Rune, utf8.RuneError) {
|
||||||
|
dst = append(dst, string(re.Rune))
|
||||||
|
}
|
||||||
|
case syntax.OpCapture, syntax.OpPlus:
|
||||||
|
dst = requiredLiterals(re.Sub[0], dst)
|
||||||
|
case syntax.OpRepeat:
|
||||||
|
if re.Min > 0 {
|
||||||
|
dst = requiredLiterals(re.Sub[0], dst)
|
||||||
|
}
|
||||||
|
case syntax.OpConcat:
|
||||||
|
for _, sub := range re.Sub {
|
||||||
|
dst = requiredLiterals(sub, dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*RegexMatcher) Type() Type {
|
func (*RegexMatcher) Type() Type {
|
||||||
@@ -89,6 +126,11 @@ func (m *RegexMatcher) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *RegexMatcher) Match(s string) bool {
|
func (m *RegexMatcher) Match(s string) bool {
|
||||||
|
for _, l := range m.literals {
|
||||||
|
if !strings.Contains(s, l) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
return m.pattern.MatchString(s)
|
return m.pattern.MatchString(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,11 +144,7 @@ func (t Type) New(pattern string) (Matcher, error) {
|
|||||||
case Domain:
|
case Domain:
|
||||||
return DomainMatcher(pattern), nil
|
return DomainMatcher(pattern), nil
|
||||||
case Regex: // 1. regex matching is case-sensitive
|
case Regex: // 1. regex matching is case-sensitive
|
||||||
regex, err := regexp.Compile(pattern)
|
return newRegexMatcher(pattern)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &RegexMatcher{pattern: regex}, nil
|
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("unknown matcher type")
|
return nil, errors.New("unknown matcher type")
|
||||||
}
|
}
|
||||||
@@ -135,11 +173,7 @@ func (t Type) NewDomainPattern(pattern string) (Matcher, error) {
|
|||||||
}
|
}
|
||||||
return DomainMatcher(pattern), nil
|
return DomainMatcher(pattern), nil
|
||||||
case Regex: // Regex's charset not in LDH subset
|
case Regex: // Regex's charset not in LDH subset
|
||||||
regex, err := regexp.Compile(pattern)
|
return newRegexMatcher(pattern)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &RegexMatcher{pattern: regex}, nil
|
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("unknown matcher type")
|
return nil, errors.New("unknown matcher type")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package strmatcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var regexLiteralCases = []struct {
|
||||||
|
pattern string
|
||||||
|
literals []string
|
||||||
|
}{
|
||||||
|
{`(^|\.)91porn\.(best|com)$`, []string{"91porn."}},
|
||||||
|
{`.+\.awsdns-cn-[0-9][0-9]\.(biz|com|net|top)$`, []string{".awsdns-cn-", "."}},
|
||||||
|
{`^r+[0-9]+(---|\.)sn-(2x3|ni5|j5o)\w{5}\.googlevideo\.com$`, []string{".googlevideo.com", "sn-", "r"}},
|
||||||
|
{`(?i)abc`, nil},
|
||||||
|
{`ab(?i:CD)ef`, []string{"ab", "ef"}},
|
||||||
|
{`(abc)?x`, []string{"x"}},
|
||||||
|
{`(abc)*x`, []string{"x"}},
|
||||||
|
{`x{0,3}yy`, []string{"yy"}},
|
||||||
|
{`(ab)+c{2}`, []string{"ab", "c"}},
|
||||||
|
{`abc|abd`, []string{"ab"}},
|
||||||
|
{`\Qa.b\E`, []string{"a.b"}},
|
||||||
|
{`a\x{FFFD}b`, nil},
|
||||||
|
{`^[^.]+$`, nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexRequiredLiterals(t *testing.T) {
|
||||||
|
for _, test := range regexLiteralCases {
|
||||||
|
m, err := newRegexMatcher(test.pattern)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := m.(*RegexMatcher).literals; !slices.Equal(got, test.literals) {
|
||||||
|
t.Errorf("%s: got %q, want %q", test.pattern, got, test.literals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzRegexMatcher(f *testing.F) {
|
||||||
|
inputs := []string{
|
||||||
|
"", "x", "yy", "abd", "ccc", "ABC", "abCDef", "abcdef", "abababcc", "a.b", "a\xffb", "a\uFFFDb",
|
||||||
|
"www.91porn.com", "ns1.awsdns-cn-01.top", "r1---sn-2x3abcde.googlevideo.com",
|
||||||
|
}
|
||||||
|
for _, test := range regexLiteralCases {
|
||||||
|
for _, s := range inputs {
|
||||||
|
f.Add(test.pattern, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.Fuzz(func(t *testing.T, pattern, s string) {
|
||||||
|
re, err := regexp.Compile(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m, _ := newRegexMatcher(pattern)
|
||||||
|
if got, want := m.Match(s), re.MatchString(s); got != want {
|
||||||
|
t.Errorf("pattern %q, input %q: got %v, want %v", pattern, s, got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
+21
-25
@@ -1,7 +1,7 @@
|
|||||||
package log // import "github.com/xtls/xray-core/common/log"
|
package log // import "github.com/xtls/xray-core/common/log"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
)
|
)
|
||||||
@@ -29,36 +29,32 @@ func (m *GeneralMessage) String() string {
|
|||||||
|
|
||||||
// Record writes a message into log stream.
|
// Record writes a message into log stream.
|
||||||
func Record(msg Message) {
|
func Record(msg Message) {
|
||||||
logHandler.Handle(msg)
|
if h := logHandler.Load(); h != nil {
|
||||||
|
(*h).Handle(msg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var logHandler syncHandler
|
type SeverityLogger interface {
|
||||||
|
Handler
|
||||||
|
Severity() Severity
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSeverity() Severity {
|
||||||
|
if h := logHandler.Load(); h != nil {
|
||||||
|
if sh, ok := (*h).(SeverityLogger); ok {
|
||||||
|
return sh.Severity()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// log everything by default
|
||||||
|
return Severity_Debug
|
||||||
|
}
|
||||||
|
|
||||||
|
var logHandler atomic.Pointer[Handler]
|
||||||
|
|
||||||
// RegisterHandler registers a new handler as current log handler. Previous registered handler will be discarded.
|
// RegisterHandler registers a new handler as current log handler. Previous registered handler will be discarded.
|
||||||
func RegisterHandler(handler Handler) {
|
func RegisterHandler(handler Handler) {
|
||||||
if handler == nil {
|
if handler == nil {
|
||||||
panic("Log handler is nil")
|
panic("Log handler is nil")
|
||||||
}
|
}
|
||||||
logHandler.Set(handler)
|
logHandler.Store(&handler)
|
||||||
}
|
|
||||||
|
|
||||||
type syncHandler struct {
|
|
||||||
sync.RWMutex
|
|
||||||
Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *syncHandler) Handle(msg Message) {
|
|
||||||
h.RLock()
|
|
||||||
defer h.RUnlock()
|
|
||||||
|
|
||||||
if h.Handler != nil {
|
|
||||||
h.Handler.Handle(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *syncHandler) Set(handler Handler) {
|
|
||||||
h.Lock()
|
|
||||||
defer h.Unlock()
|
|
||||||
|
|
||||||
h.Handler = handler
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,10 @@ func (l *serverityLogger) Handle(msg Message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *serverityLogger) Severity() Severity {
|
||||||
|
return l.logLevel
|
||||||
|
}
|
||||||
|
|
||||||
func (l *generalLogger) run() {
|
func (l *generalLogger) run() {
|
||||||
defer l.access.Signal()
|
defer l.access.Signal()
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func (m *ClientManager) Dispatch(ctx context.Context, link *transport.Link) erro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New("unable to find an available mux client").AtWarning()
|
return errors.New("unable to find an available mux client")
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorkerPicker interface {
|
type WorkerPicker interface {
|
||||||
|
|||||||
+1
-1
@@ -117,7 +117,7 @@ func (f *FrameMetadata) Unmarshal(reader io.Reader, readSourceAndLocal bool) err
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if metaLen > 512 {
|
if metaLen > 512 {
|
||||||
return errors.New("invalid metalen ", metaLen).AtError()
|
return errors.New("invalid metalen ", metaLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
b := buf.New()
|
b := buf.New()
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ func (w *ServerWorker) handleFrame(ctx context.Context, reader *buf.BufferedRead
|
|||||||
err = w.handleStatusKeep(&meta, reader)
|
err = w.handleStatusKeep(&meta, reader)
|
||||||
default:
|
default:
|
||||||
status := meta.SessionStatus
|
status := meta.SessionStatus
|
||||||
return errors.New("unknown status: ", status).AtError()
|
return errors.New("unknown status: ", status)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build darwin
|
//go:build darwin && !ios
|
||||||
|
|
||||||
package net
|
package net
|
||||||
|
|
||||||
@@ -198,7 +198,9 @@ func darwinSocketInfoMatchLevel(info []byte, network string, srcAddr netip.Addr,
|
|||||||
|
|
||||||
vflag := info[darwinInSockInfoVFlagOff]
|
vflag := info[darwinInSockInfoVFlagOff]
|
||||||
if srcAddr.Is4() {
|
if srcAddr.Is4() {
|
||||||
if family != unix.AF_INET || vflag&darwinInSockInfoIPv4 == 0 {
|
// Dual-stack sockets expose IPv4-mapped connections as AF_INET6
|
||||||
|
// while marking the endpoint as IPv4 in ini_vflag.
|
||||||
|
if (family != unix.AF_INET && family != unix.AF_INET6) || vflag&darwinInSockInfoIPv4 == 0 {
|
||||||
return darwinSocketNoMatch
|
return darwinSocketNoMatch
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build darwin
|
//go:build darwin && !ios
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build darwin
|
//go:build darwin && !ios
|
||||||
|
|
||||||
package net
|
package net
|
||||||
|
|
||||||
@@ -52,6 +52,57 @@ func TestFindProcessDarwinTCP(t *testing.T) {
|
|||||||
assertCurrentProcess(t, pid, name, path)
|
assertCurrentProcess(t, pid, name, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFindProcessDarwinTCPIPv4Mapped(t *testing.T) {
|
||||||
|
listener, err := stdnet.Listen("tcp4", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer listener.Close()
|
||||||
|
|
||||||
|
accepted := make(chan stdnet.Conn, 1)
|
||||||
|
go func() {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err == nil {
|
||||||
|
accepted <- conn
|
||||||
|
return
|
||||||
|
}
|
||||||
|
close(accepted)
|
||||||
|
}()
|
||||||
|
|
||||||
|
listenerAddr := listener.Addr().(*stdnet.TCPAddr)
|
||||||
|
fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_STREAM, unix.IPPROTO_TCP)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer unix.Close(fd)
|
||||||
|
|
||||||
|
mappedAddr := [16]byte{10: 0xff, 11: 0xff, 12: 127, 15: 1}
|
||||||
|
if err := unix.Connect(fd, &unix.SockaddrInet6{
|
||||||
|
Port: listenerAddr.Port,
|
||||||
|
Addr: mappedAddr,
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
serverConn := <-accepted
|
||||||
|
if serverConn == nil {
|
||||||
|
t.Fatal("server did not accept tcp connection")
|
||||||
|
}
|
||||||
|
defer serverConn.Close()
|
||||||
|
|
||||||
|
local, err := unix.Getsockname(fd)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
localPort := local.(*unix.SockaddrInet6).Port
|
||||||
|
|
||||||
|
pid, name, path, err := FindProcess("tcp", "127.0.0.1", uint16(localPort), "127.0.0.1", uint16(listenerAddr.Port))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertCurrentProcess(t, pid, name, path)
|
||||||
|
}
|
||||||
|
|
||||||
func TestFindProcessDarwinUDP(t *testing.T) {
|
func TestFindProcessDarwinUDP(t *testing.T) {
|
||||||
conn, err := stdnet.ListenUDP("udp", &stdnet.UDPAddr{IP: stdnet.ParseIP("127.0.0.1")})
|
conn, err := stdnet.ListenUDP("udp", &stdnet.UDPAddr{IP: stdnet.ParseIP("127.0.0.1")})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -264,6 +315,18 @@ func TestDarwinSocketInfoMatchLevelFallbacks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDarwinSocketInfoMatchLevelIPv4Mapped(t *testing.T) {
|
||||||
|
src := netip.MustParseAddr("127.0.0.1")
|
||||||
|
dst := netip.MustParseAddr("203.0.113.10")
|
||||||
|
info := newDarwinSocketInfo("tcp", src, 12345, dst, 443)
|
||||||
|
writeDarwinNativeUint32(info, darwinSocketInfoFamilyOff, uint32(unix.AF_INET6))
|
||||||
|
|
||||||
|
level := darwinSocketInfoMatchLevel(info, "tcp", src, 12345, dst, 443, true)
|
||||||
|
if level != darwinSocketExactMatch {
|
||||||
|
t.Fatalf("unexpected match level: got %d, want %d", level, darwinSocketExactMatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newDarwinSocketInfo(network string, local netip.Addr, localPort uint16, remote netip.Addr, remotePort uint16) []byte {
|
func newDarwinSocketInfo(network string, local netip.Addr, localPort uint16, remote netip.Addr, remotePort uint16) []byte {
|
||||||
info := make([]byte, darwinSocketFDInfoSize)
|
info := make([]byte, darwinSocketFDInfoSize)
|
||||||
switch network {
|
switch network {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
//go:build ios
|
||||||
|
|
||||||
|
package net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FindProcess(network, srcIP string, srcPort uint16, destIP string, destPort uint16) (int, string, string, error) {
|
||||||
|
return 0, "", "", errors.New("process lookup is not supported on this platform")
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package platform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var envReloadRegistry = struct {
|
||||||
|
sync.RWMutex
|
||||||
|
handlers []func() error
|
||||||
|
}{}
|
||||||
|
|
||||||
|
// RegisterEnvReload registers an environment reload handler and runs it once
|
||||||
|
// immediately so package defaults keep the same behavior as init-time reads.
|
||||||
|
func RegisterEnvReload(handler func() error) {
|
||||||
|
if handler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
envReloadRegistry.Lock()
|
||||||
|
envReloadRegistry.handlers = append(envReloadRegistry.handlers, handler)
|
||||||
|
envReloadRegistry.Unlock()
|
||||||
|
if err := handler(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReloadEnvSettings refreshes all registered environment-backed package state.
|
||||||
|
func ReloadEnvSettings() error {
|
||||||
|
envReloadRegistry.RLock()
|
||||||
|
handlers := append([]func() error{}, envReloadRegistry.handlers...)
|
||||||
|
envReloadRegistry.RUnlock()
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
for _, handler := range handlers {
|
||||||
|
if err := handler(); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
@@ -3,11 +3,8 @@ package bittorrent
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"math"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SniffHeader struct{}
|
type SniffHeader struct{}
|
||||||
@@ -39,50 +36,44 @@ func SniffUTP(b []byte) (*SniffHeader, error) {
|
|||||||
return nil, common.ErrNoClue
|
return nil, common.ErrNoClue
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer := buf.FromBytes(b)
|
// type 4 (ST_SYN), version 1
|
||||||
|
if b[0] != 0x41 {
|
||||||
var typeAndVersion uint8
|
|
||||||
|
|
||||||
if binary.Read(buffer, binary.BigEndian, &typeAndVersion) != nil {
|
|
||||||
return nil, common.ErrNoClue
|
|
||||||
} else if b[0]>>4&0xF > 4 || b[0]&0xF != 1 {
|
|
||||||
return nil, errNotBittorrent
|
return nil, errNotBittorrent
|
||||||
}
|
}
|
||||||
|
|
||||||
var extension uint8
|
// timestamp_difference is always 0 in new connections
|
||||||
|
if binary.BigEndian.Uint32(b[8:12]) != 0 {
|
||||||
if binary.Read(buffer, binary.BigEndian, &extension) != nil {
|
|
||||||
return nil, common.ErrNoClue
|
|
||||||
} else if extension != 0 && extension != 1 {
|
|
||||||
return nil, errNotBittorrent
|
return nil, errNotBittorrent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Walk the extension chain. Selective ack (1) and extension bits (2)
|
||||||
|
extension, offset := b[1], 20
|
||||||
for extension != 0 {
|
for extension != 0 {
|
||||||
if extension != 1 {
|
if len(b) < offset+2 {
|
||||||
return nil, errNotBittorrent
|
return nil, errNotBittorrent
|
||||||
}
|
}
|
||||||
if binary.Read(buffer, binary.BigEndian, &extension) != nil {
|
length := int(b[offset+1])
|
||||||
return nil, common.ErrNoClue
|
switch extension {
|
||||||
|
case 1: // selective ack
|
||||||
|
if length < 4 || length%4 != 0 {
|
||||||
|
return nil, errNotBittorrent
|
||||||
|
}
|
||||||
|
case 2: // extension bits: fixed 8 bytes, sent in ST_SYN by µTorrent
|
||||||
|
if length != 8 {
|
||||||
|
return nil, errNotBittorrent
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, errNotBittorrent
|
||||||
}
|
}
|
||||||
|
if len(b) < offset+2+length {
|
||||||
var length uint8
|
return nil, errNotBittorrent
|
||||||
if err := binary.Read(buffer, binary.BigEndian, &length); err != nil {
|
|
||||||
return nil, common.ErrNoClue
|
|
||||||
}
|
|
||||||
if common.Error2(buffer.ReadBytes(int32(length))) != nil {
|
|
||||||
return nil, common.ErrNoClue
|
|
||||||
}
|
}
|
||||||
|
extension = b[offset]
|
||||||
|
offset += 2 + length
|
||||||
}
|
}
|
||||||
|
|
||||||
if common.Error2(buffer.ReadBytes(2)) != nil {
|
// extensions should consume all ST_SYN payload
|
||||||
return nil, common.ErrNoClue
|
if len(b) != offset {
|
||||||
}
|
|
||||||
|
|
||||||
var timestamp uint32
|
|
||||||
if err := binary.Read(buffer, binary.BigEndian, ×tamp); err != nil {
|
|
||||||
return nil, common.ErrNoClue
|
|
||||||
}
|
|
||||||
if math.Abs(float64(time.Now().UnixMicro()-int64(timestamp))) > float64(24*time.Hour) {
|
|
||||||
return nil, errNotBittorrent
|
return nil, errNotBittorrent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package bittorrent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// utpPacket builds the fixed 20-byte header defined by BEP 29.
|
||||||
|
func utpPacket(packetType, extension byte, tsDiff uint32, payload ...byte) []byte {
|
||||||
|
b := make([]byte, 20)
|
||||||
|
b[0] = packetType<<4 | 1
|
||||||
|
b[1] = extension
|
||||||
|
binary.BigEndian.PutUint16(b[2:4], 0x4a3f) // connection_id, random
|
||||||
|
binary.BigEndian.PutUint32(b[4:8], 0x8c3a91d2) // timestamp_microseconds, sender's clock
|
||||||
|
binary.BigEndian.PutUint32(b[8:12], tsDiff)
|
||||||
|
binary.BigEndian.PutUint32(b[12:16], 0x00100000) // wnd_size
|
||||||
|
binary.BigEndian.PutUint16(b[16:18], 0x71ee) // seq_nr, random in libutp/libtorrent
|
||||||
|
binary.BigEndian.PutUint16(b[18:20], 0x0000) // ack_nr
|
||||||
|
return append(b, payload...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSniffUTP(t *testing.T) {
|
||||||
|
selectiveAck := []byte{0, 4, 0xff, 0x00, 0xff, 0x00}
|
||||||
|
wrongVersion := utpPacket(4, 0, 0)
|
||||||
|
wrongVersion[0] = 4<<4 | 2
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
payload []byte
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{"syn", utpPacket(4, 0, 0), nil},
|
||||||
|
{"syn with selective ack", append(utpPacket(4, 1, 0), selectiveAck...), nil},
|
||||||
|
{"syn with extension bits", append(utpPacket(4, 2, 0), 0, 8, 1, 2, 3, 4, 5, 6, 7, 8), nil},
|
||||||
|
{"extension bits with wrong length", append(utpPacket(4, 2, 0), 0, 4, 1, 2, 3, 4), errNotBittorrent},
|
||||||
|
{"syn with nonzero timestamp_difference", utpPacket(4, 0, 0x1234), errNotBittorrent},
|
||||||
|
{"syn with trailing payload", utpPacket(4, 0, 0, 'x'), errNotBittorrent},
|
||||||
|
// txid 0x4100, no EDNS0: the worst case colliding with the uTP header
|
||||||
|
{"dns query", []byte{
|
||||||
|
0x41, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x01, 'a', 0x02, 'c', 'o', 0x00, 0x00, 0x01, 0x00, 0x01,
|
||||||
|
}, errNotBittorrent},
|
||||||
|
{"established connection packets", utpPacket(0, 0, 0x5678, 'x', 'y', 'z'), errNotBittorrent},
|
||||||
|
{"state", utpPacket(2, 0, 0x5678), errNotBittorrent},
|
||||||
|
{"fin", utpPacket(1, 0, 0x5678), errNotBittorrent},
|
||||||
|
{"wrong version", wrongVersion, errNotBittorrent},
|
||||||
|
{"unknown packet type", utpPacket(5, 0, 0), errNotBittorrent},
|
||||||
|
{"unknown extension", utpPacket(4, 2, 0), errNotBittorrent},
|
||||||
|
{"extension chain past the datagram", utpPacket(4, 1, 0, 0, 8, 0xff), errNotBittorrent},
|
||||||
|
{"selective ack not in multiples of 4", append(utpPacket(4, 1, 0), 0, 3, 0xff, 0x00, 0xff), errNotBittorrent},
|
||||||
|
{"shorter than the header", utpPacket(4, 0, 0)[:19], common.ErrNoClue},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
h, err := SniffUTP(c.payload)
|
||||||
|
if err != c.err {
|
||||||
|
t.Fatalf("expected error %v, got %v", c.err, err)
|
||||||
|
}
|
||||||
|
if err == nil && h == nil {
|
||||||
|
t.Fatal("expected a sniff header, got nil")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,10 @@
|
|||||||
package quic
|
package quic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
_ "crypto/tls"
|
_ "crypto/tls"
|
||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CipherSuiteTLS13 struct {
|
|
||||||
ID uint16
|
|
||||||
KeyLen int
|
|
||||||
AEAD func(key, fixedNonce []byte) cipher.AEAD
|
|
||||||
Hash crypto.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname AEADAESGCMTLS13 crypto/tls.aeadAESGCMTLS13
|
//go:linkname AEADAESGCMTLS13 crypto/tls.aeadAESGCMTLS13
|
||||||
func AEADAESGCMTLS13(key, nonceMask []byte) cipher.AEAD
|
func AEADAESGCMTLS13(key, nonceMask []byte) cipher.AEAD
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package quic
|
|||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/tls"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
@@ -28,22 +27,43 @@ func (s SniffHeader) Domain() string {
|
|||||||
return s.domain
|
return s.domain
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
var (
|
||||||
versionDraft29 uint32 = 0xff00001d
|
errNotQUIC = errors.New("not quic")
|
||||||
version1 uint32 = 0x1
|
errNotQUICInitial = errors.New("not initial packet")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type quicVersionSpec struct {
|
||||||
|
ver uint32
|
||||||
|
typeInitial byte
|
||||||
|
initialSalt []byte
|
||||||
|
labelPrefix string
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
quicSaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
|
quicDraft29 = quicVersionSpec{
|
||||||
quicSalt = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
|
ver: 0xff00001d,
|
||||||
initialSuite = &CipherSuiteTLS13{
|
typeInitial: 0b00,
|
||||||
ID: tls.TLS_AES_128_GCM_SHA256,
|
initialSalt: []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99},
|
||||||
KeyLen: 16,
|
labelPrefix: "quic",
|
||||||
AEAD: AEADAESGCMTLS13,
|
}
|
||||||
Hash: crypto.SHA256,
|
quicV1 = quicVersionSpec{
|
||||||
|
ver: 0x1,
|
||||||
|
typeInitial: 0b00,
|
||||||
|
initialSalt: []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a},
|
||||||
|
labelPrefix: "quic",
|
||||||
|
}
|
||||||
|
quicV2 = quicVersionSpec{
|
||||||
|
ver: 0x6b3343cf,
|
||||||
|
typeInitial: 0b01,
|
||||||
|
initialSalt: []byte{0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9},
|
||||||
|
labelPrefix: "quicv2",
|
||||||
|
}
|
||||||
|
|
||||||
|
quicVersionSpecMap = map[uint32]*quicVersionSpec{
|
||||||
|
quicDraft29.ver: &quicDraft29,
|
||||||
|
quicV1.ver: &quicV1,
|
||||||
|
quicV2.ver: &quicV2,
|
||||||
}
|
}
|
||||||
errNotQuic = errors.New("not quic")
|
|
||||||
errNotQuicInitial = errors.New("not initial packet")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func SniffQUIC(b []byte) (*SniffHeader, error) {
|
func SniffQUIC(b []byte) (*SniffHeader, error) {
|
||||||
@@ -63,60 +83,61 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
buffer := buf.FromBytes(b)
|
buffer := buf.FromBytes(b)
|
||||||
typeByte, err := buffer.ReadByte()
|
typeByte, err := buffer.ReadByte()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
}
|
}
|
||||||
|
|
||||||
isLongHeader := typeByte&0x80 > 0
|
isLongHeader := typeByte&0x80 > 0
|
||||||
if !isLongHeader || typeByte&0x40 == 0 {
|
if !isLongHeader || typeByte&0x40 == 0 {
|
||||||
return nil, errNotQuicInitial
|
return nil, errNotQUICInitial
|
||||||
}
|
}
|
||||||
|
|
||||||
vb, err := buffer.ReadBytes(4)
|
vb, err := buffer.ReadBytes(4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
}
|
}
|
||||||
|
|
||||||
versionNumber := binary.BigEndian.Uint32(vb)
|
versionNumber := binary.BigEndian.Uint32(vb)
|
||||||
if versionNumber != 0 && typeByte&0x40 == 0 {
|
var s *quicVersionSpec
|
||||||
return nil, errNotQuic
|
if v, ok := quicVersionSpecMap[versionNumber]; ok {
|
||||||
} else if versionNumber != versionDraft29 && versionNumber != version1 {
|
s = v
|
||||||
return nil, errNotQuic
|
} else {
|
||||||
|
return nil, errNotQUIC
|
||||||
}
|
}
|
||||||
|
|
||||||
packetType := (typeByte & 0x30) >> 4
|
|
||||||
isQuicInitial := packetType == 0x0
|
|
||||||
|
|
||||||
var destConnID []byte
|
var destConnID []byte
|
||||||
if l, err := buffer.ReadByte(); err != nil {
|
if l, err := buffer.ReadByte(); err != nil {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
} else if destConnID, err = buffer.ReadBytes(int32(l)); err != nil {
|
} else if destConnID, err = buffer.ReadBytes(int32(l)); err != nil {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
}
|
}
|
||||||
|
|
||||||
if l, err := buffer.ReadByte(); err != nil {
|
if l, err := buffer.ReadByte(); err != nil {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
} else if common.Error2(buffer.ReadBytes(int32(l))) != nil {
|
} else if common.Error2(buffer.ReadBytes(int32(l))) != nil {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
}
|
}
|
||||||
|
|
||||||
if isQuicInitial { // Only initial packets have token, see https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.2
|
packetType := (typeByte & 0x30) >> 4
|
||||||
tokenLen, err := readShortQuicVarint(buffer)
|
isQUICInitial := packetType == s.typeInitial
|
||||||
|
|
||||||
|
if isQUICInitial { // Only initial packets have token, see https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.2
|
||||||
|
tokenLen, err := readShortQUICVarint(buffer)
|
||||||
if err != nil || tokenLen > int32(len(b)) {
|
if err != nil || tokenLen > int32(len(b)) {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = buffer.ReadBytes(tokenLen); err != nil {
|
if _, err = buffer.ReadBytes(tokenLen); err != nil {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
packetLen, err := readShortQuicVarint(buffer)
|
packetLen, err := readShortQUICVarint(buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
}
|
}
|
||||||
// packetLen is impossible to be shorter than this
|
// packetLen is impossible to be shorter than this
|
||||||
if packetLen < 4 {
|
if packetLen < 4 {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
}
|
}
|
||||||
|
|
||||||
hdrLen := len(b) - int(buffer.Len())
|
hdrLen := len(b) - int(buffer.Len())
|
||||||
@@ -125,25 +146,23 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
restPayload := b[hdrLen+int(packetLen):]
|
restPayload := b[hdrLen+int(packetLen):]
|
||||||
if !isQuicInitial { // Skip this packet if it's not initial packet
|
if !isQUICInitial { // Skip this packet if it's not initial packet
|
||||||
b = restPayload
|
b = restPayload
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var salt []byte
|
salt := s.initialSalt
|
||||||
if versionNumber == version1 {
|
label := s.labelPrefix
|
||||||
salt = quicSalt
|
|
||||||
} else {
|
|
||||||
salt = quicSaltOld
|
|
||||||
}
|
|
||||||
initialSecret := hkdf.Extract(crypto.SHA256.New, destConnID, salt)
|
initialSecret := hkdf.Extract(crypto.SHA256.New, destConnID, salt)
|
||||||
secret := hkdfExpandLabel(crypto.SHA256, initialSecret, []byte{}, "client in", crypto.SHA256.Size())
|
secret := hkdfExpandLabel(initialSecret, "client in", crypto.SHA256.Size())
|
||||||
hpKey := hkdfExpandLabel(initialSuite.Hash, secret, []byte{}, "quic hp", initialSuite.KeyLen)
|
hpKey := hkdfExpandLabel(secret, label+" hp", 16)
|
||||||
block, err := aes.NewCipher(hpKey)
|
block, err := aes.NewCipher(hpKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(b) < hdrLen+4+block.BlockSize() {
|
||||||
|
return nil, errNotQUIC
|
||||||
|
}
|
||||||
cache.Clear()
|
cache.Clear()
|
||||||
mask := cache.Extend(int32(block.BlockSize()))
|
mask := cache.Extend(int32(block.BlockSize()))
|
||||||
block.Encrypt(mask, b[hdrLen+4:hdrLen+4+len(mask)])
|
block.Encrypt(mask, b[hdrLen+4:hdrLen+4+len(mask)])
|
||||||
@@ -153,8 +172,8 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
b[hdrLen+i] ^= mask[i+1]
|
b[hdrLen+i] ^= mask[i+1]
|
||||||
}
|
}
|
||||||
|
|
||||||
key := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic key", 16)
|
key := hkdfExpandLabel(secret, label+" key", 16)
|
||||||
iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12)
|
iv := hkdfExpandLabel(secret, label+" iv", 12)
|
||||||
cipher := AEADAESGCMTLS13(key, iv)
|
cipher := AEADAESGCMTLS13(key, iv)
|
||||||
|
|
||||||
nonce := cache.Extend(int32(cipher.NonceSize()))
|
nonce := cache.Extend(int32(cipher.NonceSize()))
|
||||||
@@ -179,44 +198,44 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
case 0x00: // PADDING frame
|
case 0x00: // PADDING frame
|
||||||
case 0x01: // PING frame
|
case 0x01: // PING frame
|
||||||
case 0x02, 0x03: // ACK frame
|
case 0x02, 0x03: // ACK frame
|
||||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Largest Acknowledged
|
if _, err = readShortQUICVarint(buffer); err != nil { // Field: Largest Acknowledged
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Delay
|
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Delay
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
ackRangeCount, err := readShortQuicVarint(buffer) // Field: ACK Range Count
|
ackRangeCount, err := readShortQUICVarint(buffer) // Field: ACK Range Count
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: First ACK Range
|
if _, err = readShortQUICVarint(buffer); err != nil { // Field: First ACK Range
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range
|
for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range
|
||||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Range -> Gap
|
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Range -> Gap
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Range -> ACK Range Length
|
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Range -> ACK Range Length
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if frameType == 0x03 {
|
if frameType == 0x03 {
|
||||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ECN Counts -> ECT0 Count
|
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ECN Counts -> ECT0 Count
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ECN Counts -> ECT1 Count
|
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ECN Counts -> ECT1 Count
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQuicVarint(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count
|
if _, err = readShortQUICVarint(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 0x06: // CRYPTO frame, we will use this frame
|
case 0x06: // CRYPTO frame, we will use this frame
|
||||||
offset, err := readShortQuicVarint(buffer) // Field: Offset
|
offset, err := readShortQUICVarint(buffer) // Field: Offset
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
length, err := readShortQuicVarint(buffer) // Field: Length
|
length, err := readShortQUICVarint(buffer) // Field: Length
|
||||||
if err != nil || length > buffer.Len() {
|
if err != nil || length > buffer.Len() {
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
@@ -232,13 +251,13 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet
|
case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet
|
||||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Error Code
|
if _, err = readShortQUICVarint(buffer); err != nil { // Field: Error Code
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Frame Type
|
if _, err = readShortQUICVarint(buffer); err != nil { // Field: Frame Type
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
length, err := readShortQuicVarint(buffer) // Field: Reason Phrase Length
|
length, err := readShortQUICVarint(buffer) // Field: Reason Phrase Length
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
@@ -248,7 +267,7 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
default:
|
default:
|
||||||
// Only above frame types are permitted in initial packet.
|
// Only above frame types are permitted in initial packet.
|
||||||
// See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8
|
// See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8
|
||||||
return nil, errNotQuicInitial
|
return nil, errNotQUICInitial
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,35 +285,33 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
return nil, protocol.ErrProtoNeedMoreData
|
return nil, protocol.ErrProtoNeedMoreData
|
||||||
}
|
}
|
||||||
|
|
||||||
func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte {
|
func hkdfExpandLabel(secret []byte, label string, length int) []byte {
|
||||||
b := make([]byte, 3, 3+6+len(label)+1+len(context))
|
b := make([]byte, 0, 2+1+6+len(label)+1)
|
||||||
binary.BigEndian.PutUint16(b, uint16(length))
|
b = binary.BigEndian.AppendUint16(b, uint16(length))
|
||||||
b[2] = uint8(6 + len(label))
|
b = append(b, byte(6+len(label)))
|
||||||
b = append(b, []byte("tls13 ")...)
|
b = append(b, "tls13 "...)
|
||||||
b = append(b, []byte(label)...)
|
b = append(b, label...)
|
||||||
b = b[:3+6+len(label)+1]
|
b = append(b, 0) // context
|
||||||
b[3+6+len(label)] = uint8(len(context))
|
|
||||||
b = append(b, context...)
|
|
||||||
|
|
||||||
out := make([]byte, length)
|
out := make([]byte, length)
|
||||||
n, err := hkdf.Expand(hash.New, secret, b).Read(out)
|
n, err := hkdf.Expand(crypto.SHA256.New, secret, b).Read(out)
|
||||||
if err != nil || n != length {
|
if err != nil || n != length {
|
||||||
panic("quic: HKDF-Expand-Label invocation failed unexpectedly")
|
panic("quic: HKDF-Expand-Label invocation failed unexpectedly")
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// readShortQuicVarint wraps quicvarint.Read with a max limit for length related fields.
|
// readShortQUICVarint wraps quicvarint.Read with a max limit for length related fields.
|
||||||
// we only handle QUIC Initial so these numbers should not exceed 65535
|
// we only handle QUIC Initial so these numbers should not exceed 65535
|
||||||
// returns int32 to reduce type conversion
|
// returns int32 to reduce type conversion
|
||||||
func readShortQuicVarint(reader io.ByteReader) (int32, error) {
|
func readShortQUICVarint(reader io.ByteReader) (int32, error) {
|
||||||
v, err := quicvarint.Read(reader)
|
v, err := quicvarint.Read(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
if v > 65535 {
|
if v > 65535 {
|
||||||
// not used(
|
// not used(
|
||||||
return 0, errNotQuicInitial
|
return 0, errNotQUICInitial
|
||||||
}
|
}
|
||||||
return int32(v), nil
|
return int32(v), nil
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
func (u *User) GetTypedAccount() (Account, error) {
|
func (u *User) GetTypedAccount() (Account, error) {
|
||||||
if u.GetAccount() == nil {
|
if u.GetAccount() == nil {
|
||||||
return nil, errors.New("Account is missing").AtWarning()
|
return nil, errors.New("Account is missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
rawAccount, err := u.Account.GetInstance()
|
rawAccount, err := u.Account.GetInstance()
|
||||||
|
|||||||
@@ -207,6 +207,7 @@ func getConfig() string {
|
|||||||
"tag": "XHTTP_IN",
|
"tag": "XHTTP_IN",
|
||||||
"streamSettings": {
|
"streamSettings": {
|
||||||
"network": "xhttp",
|
"network": "xhttp",
|
||||||
|
"security": "tls",
|
||||||
"xhttpSettings": {
|
"xhttpSettings": {
|
||||||
"host": "bing.com",
|
"host": "bing.com",
|
||||||
"path": "/xhttp_client_upload",
|
"path": "/xhttp_client_upload",
|
||||||
|
|||||||
@@ -70,8 +70,6 @@ type Outbound struct {
|
|||||||
Tag string
|
Tag string
|
||||||
// Name of the outbound proxy that handles the connection.
|
// Name of the outbound proxy that handles the connection.
|
||||||
Name string
|
Name string
|
||||||
// Unused. Conn is actually internet.Connection. May be nil. It is currently nil for outbound with proxySettings
|
|
||||||
Conn net.Conn
|
|
||||||
// CanSpliceCopy is a property for this connection
|
// CanSpliceCopy is a property for this connection
|
||||||
// 1 = can, 2 = after processing protocol info should be able to, 3 = cannot
|
// 1 = can, 2 = after processing protocol info should be able to, 3 = cannot
|
||||||
CanSpliceCopy int
|
CanSpliceCopy int
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ToNetwork(network string) net.Network {
|
|
||||||
switch N.NetworkName(network) {
|
|
||||||
case N.NetworkTCP:
|
|
||||||
return net.Network_TCP
|
|
||||||
case N.NetworkUDP:
|
|
||||||
return net.Network_UDP
|
|
||||||
default:
|
|
||||||
return net.Network_Unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToDestination(socksaddr M.Socksaddr, network net.Network) (net.Destination, error) {
|
|
||||||
// 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
|
|
||||||
if socksaddr.IsIP() {
|
|
||||||
return 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToSocksaddr(destination net.Destination) M.Socksaddr {
|
|
||||||
var addr M.Socksaddr
|
|
||||||
switch destination.Address.Family() {
|
|
||||||
case net.AddressFamilyDomain:
|
|
||||||
addr.Fqdn = destination.Address.Domain()
|
|
||||||
default:
|
|
||||||
addr.Addr = M.AddrFromIP(destination.Address.IP())
|
|
||||||
}
|
|
||||||
addr.Port = uint16(destination.Port)
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package singbridge
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/common/net/cnc"
|
|
||||||
"github.com/xtls/xray-core/common/session"
|
|
||||||
"github.com/xtls/xray-core/proxy"
|
|
||||||
"github.com/xtls/xray-core/transport"
|
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
|
||||||
"github.com/xtls/xray-core/transport/pipe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ N.Dialer = (*XrayDialer)(nil)
|
|
||||||
|
|
||||||
type XrayDialer struct {
|
|
||||||
internet.Dialer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDialer(dialer internet.Dialer) *XrayDialer {
|
|
||||||
return &XrayDialer{dialer}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *XrayDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
type XrayOutboundDialer struct {
|
|
||||||
outbound proxy.Outbound
|
|
||||||
dialer internet.Dialer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOutboundDialer(outbound proxy.Outbound, dialer internet.Dialer) *XrayOutboundDialer {
|
|
||||||
return &XrayOutboundDialer{outbound, dialer}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
opts := []pipe.Option{pipe.WithSizeLimit(64 * 1024)}
|
|
||||||
uplinkReader, uplinkWriter := pipe.New(opts...)
|
|
||||||
downlinkReader, downlinkWriter := pipe.New(opts...)
|
|
||||||
conn := cnc.NewConnection(cnc.ConnectionInputMulti(downlinkWriter), cnc.ConnectionOutputMulti(uplinkReader))
|
|
||||||
go d.outbound.Process(ctx, &transport.Link{Reader: downlinkReader, Writer: uplinkWriter}, d.dialer)
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *XrayOutboundDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package singbridge
|
|
||||||
|
|
||||||
import E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
|
|
||||||
func ReturnError(err error) error {
|
|
||||||
if E.IsClosedOrCanceled(err) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package singbridge
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/xtls/xray-core/common/buf"
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/features/routing"
|
|
||||||
"github.com/xtls/xray-core/transport"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ N.TCPConnectionHandler = (*Dispatcher)(nil)
|
|
||||||
_ N.UDPConnectionHandler = (*Dispatcher)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Dispatcher struct {
|
|
||||||
upstream routing.Dispatcher
|
|
||||||
newErrorFunc func(values ...any) *errors.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDispatcher(dispatcher routing.Dispatcher, newErrorFunc func(values ...any) *errors.Error) *Dispatcher {
|
|
||||||
return &Dispatcher{
|
|
||||||
upstream: dispatcher,
|
|
||||||
newErrorFunc: newErrorFunc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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{
|
|
||||||
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{
|
|
||||||
Reader: buf.NewPacketReader(conn.(io.Reader)),
|
|
||||||
Writer: buf.NewWriter(conn.(io.Writer)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dispatcher) NewError(ctx context.Context, err error) {
|
|
||||||
errors.LogInfo(ctx, err.Error())
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
package singbridge
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ logger.ContextLogger = (*XrayLogger)(nil)
|
|
||||||
|
|
||||||
type XrayLogger struct {
|
|
||||||
newError func(values ...any) *errors.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLogger(newErrorFunc func(values ...any) *errors.Error) *XrayLogger {
|
|
||||||
return &XrayLogger{
|
|
||||||
newErrorFunc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *XrayLogger) Trace(args ...any) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *XrayLogger) Debug(args ...any) {
|
|
||||||
errors.LogDebug(context.Background(), args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *XrayLogger) Info(args ...any) {
|
|
||||||
errors.LogInfo(context.Background(), args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *XrayLogger) Warn(args ...any) {
|
|
||||||
errors.LogWarning(context.Background(), args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *XrayLogger) Error(args ...any) {
|
|
||||||
errors.LogError(context.Background(), args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *XrayLogger) Fatal(args ...any) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *XrayLogger) Panic(args ...any) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *XrayLogger) TraceContext(ctx context.Context, args ...any) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *XrayLogger) DebugContext(ctx context.Context, args ...any) {
|
|
||||||
errors.LogDebug(ctx, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *XrayLogger) InfoContext(ctx context.Context, args ...any) {
|
|
||||||
errors.LogInfo(ctx, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *XrayLogger) WarnContext(ctx context.Context, args ...any) {
|
|
||||||
errors.LogWarning(ctx, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *XrayLogger) ErrorContext(ctx context.Context, args ...any) {
|
|
||||||
errors.LogError(ctx, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *XrayLogger) FatalContext(ctx context.Context, args ...any) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *XrayLogger) PanicContext(ctx context.Context, args ...any) {
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
package singbridge
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
B "github.com/sagernet/sing/common/buf"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
"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)))
|
|
||||||
}
|
|
||||||
|
|
||||||
type PacketConnWrapper struct {
|
|
||||||
buf.Reader
|
|
||||||
buf.Writer
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if w.cached != nil {
|
|
||||||
mb, bb := buf.SplitFirst(w.cached)
|
|
||||||
if bb == nil {
|
|
||||||
w.cached = nil
|
|
||||||
} else {
|
|
||||||
buffer.Write(bb.Bytes())
|
|
||||||
w.cached = mb
|
|
||||||
var destination net.Destination
|
|
||||||
if bb.UDP != nil {
|
|
||||||
destination = *bb.UDP
|
|
||||||
} else {
|
|
||||||
destination = w.Dest
|
|
||||||
}
|
|
||||||
bb.Release()
|
|
||||||
return ToSocksaddr(destination), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mb, err := w.ReadMultiBuffer()
|
|
||||||
nb, bb := buf.SplitFirst(mb)
|
|
||||||
if bb == nil {
|
|
||||||
return M.Socksaddr{}, nil
|
|
||||||
} else {
|
|
||||||
buffer.Write(bb.Bytes())
|
|
||||||
w.cached = nb
|
|
||||||
var destination net.Destination
|
|
||||||
if bb.UDP != nil {
|
|
||||||
destination = *bb.UDP
|
|
||||||
} else {
|
|
||||||
destination = w.Dest
|
|
||||||
}
|
|
||||||
bb.Release()
|
|
||||||
return ToSocksaddr(destination), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
vBuf := buf.New()
|
|
||||||
vBuf.Write(buffer.Bytes())
|
|
||||||
vBuf.UDP = &endpoint
|
|
||||||
return w.WriteMultiBuffer(buf.MultiBuffer{vBuf})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *PacketConnWrapper) Close() error {
|
|
||||||
buf.ReleaseMulti(w.cached)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package singbridge
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"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"
|
|
||||||
)
|
|
||||||
|
|
||||||
func CopyConn(ctx context.Context, inboundConn net.Conn, link *transport.Link, serverConn net.Conn) error {
|
|
||||||
conn := &PipeConnWrapper{
|
|
||||||
W: link.Writer,
|
|
||||||
Conn: inboundConn,
|
|
||||||
}
|
|
||||||
if ir, ok := link.Reader.(io.Reader); ok {
|
|
||||||
conn.R = ir
|
|
||||||
} 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))
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *PipeConnWrapper) Write(p []byte) (n int, err error) {
|
|
||||||
w.T.Update()
|
|
||||||
n = len(p)
|
|
||||||
var mb buf.MultiBuffer
|
|
||||||
pLen := len(p)
|
|
||||||
for pLen > 0 {
|
|
||||||
buffer := buf.New()
|
|
||||||
if pLen > buf.Size {
|
|
||||||
_, err = buffer.Write(p[:buf.Size])
|
|
||||||
p = p[buf.Size:]
|
|
||||||
} else {
|
|
||||||
buffer.Write(p)
|
|
||||||
}
|
|
||||||
pLen -= int(buffer.Len())
|
|
||||||
mb = append(mb, buffer)
|
|
||||||
}
|
|
||||||
err = w.W.WriteMultiBuffer(mb)
|
|
||||||
if err != nil {
|
|
||||||
n = 0
|
|
||||||
buf.ReleaseMulti(mb)
|
|
||||||
// downlinkonly
|
|
||||||
w.T.SetTimeout(5 * time.Second)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package singbridge
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/xtls/xray-core/common/buf"
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ buf.Reader = (*Conn)(nil)
|
|
||||||
_ buf.TimeoutReader = (*Conn)(nil)
|
|
||||||
_ buf.Writer = (*Conn)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Conn struct {
|
|
||||||
net.Conn
|
|
||||||
writer N.VectorisedWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConn(conn net.Conn) *Conn {
|
|
||||||
writer, _ := bufio.CreateVectorisedWriter(conn)
|
|
||||||
return &Conn{
|
|
||||||
Conn: conn,
|
|
||||||
writer: writer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
|
||||||
buffer, err := buf.ReadBuffer(c.Conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return buf.MultiBuffer{buffer}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) ReadMultiBufferTimeout(duration time.Duration) (buf.MultiBuffer, error) {
|
|
||||||
err := c.SetReadDeadline(time.Now().Add(duration))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer c.SetReadDeadline(time.Time{})
|
|
||||||
return c.ReadMultiBuffer()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) WriteMultiBuffer(bufferList buf.MultiBuffer) error {
|
|
||||||
defer buf.ReleaseMulti(bufferList)
|
|
||||||
if c.writer != nil {
|
|
||||||
bytesList := make([][]byte, len(bufferList))
|
|
||||||
for i, buffer := range bufferList {
|
|
||||||
bytesList[i] = buffer.Bytes()
|
|
||||||
}
|
|
||||||
return common.Error(bufio.WriteVectorised(c.writer, bytesList))
|
|
||||||
}
|
|
||||||
// Since this conn is only used by tun, we don't force buffer writes to merge.
|
|
||||||
for _, buffer := range bufferList {
|
|
||||||
_, err := c.Conn.Write(buffer.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
+2
-2
@@ -16,7 +16,7 @@ var typeCreatorRegistry = make(map[reflect.Type]ConfigCreator)
|
|||||||
func RegisterConfig(config interface{}, configCreator ConfigCreator) error {
|
func RegisterConfig(config interface{}, configCreator ConfigCreator) error {
|
||||||
configType := reflect.TypeOf(config)
|
configType := reflect.TypeOf(config)
|
||||||
if _, found := typeCreatorRegistry[configType]; found {
|
if _, found := typeCreatorRegistry[configType]; found {
|
||||||
return errors.New(configType.Name() + " is already registered").AtError()
|
return errors.New(configType.Name() + " is already registered")
|
||||||
}
|
}
|
||||||
typeCreatorRegistry[configType] = configCreator
|
typeCreatorRegistry[configType] = configCreator
|
||||||
return nil
|
return nil
|
||||||
@@ -27,7 +27,7 @@ func CreateObject(ctx context.Context, config interface{}) (interface{}, error)
|
|||||||
configType := reflect.TypeOf(config)
|
configType := reflect.TypeOf(config)
|
||||||
creator, found := typeCreatorRegistry[configType]
|
creator, found := typeCreatorRegistry[configType]
|
||||||
if !found {
|
if !found {
|
||||||
return nil, errors.New(configType.String() + " is not registered").AtError()
|
return nil, errors.New(configType.String() + " is not registered")
|
||||||
}
|
}
|
||||||
return creator(ctx, config)
|
return creator(ctx, config)
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-19
@@ -8,7 +8,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
@@ -27,25 +27,39 @@ var AddrParser = protocol.NewAddressParser(
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Show bool
|
Show atomic.Bool
|
||||||
BaseKey []byte
|
baseKey atomic.Value
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func reloadEnvSettings() error {
|
||||||
if strings.ToLower(platform.NewEnvFlag(platform.XUDPLog).GetValue(func() string { return "" })) == "true" {
|
Show.Store(strings.ToLower(platform.NewEnvFlag(platform.XUDPLog).GetValue(func() string { return "" })) == "true")
|
||||||
Show = true
|
raw := platform.NewEnvFlag(platform.XUDPBaseKey).GetValue(func() string { return "" })
|
||||||
|
if raw == "" {
|
||||||
|
ensureBaseKey()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
BaseKey = make([]byte, 32)
|
key, _ := base64.RawURLEncoding.DecodeString(raw)
|
||||||
rand.Read(BaseKey)
|
if len(key) != 32 {
|
||||||
go func() {
|
return errors.New(platform.XUDPBaseKey + ": invalid value (BaseKey must be 32 bytes): " + raw + " len " + strconv.Itoa(len(key)))
|
||||||
time.Sleep(100 * time.Millisecond) // this is not nice, but need to give some time for Android to setup ENV
|
}
|
||||||
if raw := platform.NewEnvFlag(platform.XUDPBaseKey).GetValue(func() string { return "" }); raw != "" {
|
baseKey.Store(append([]byte(nil), key...))
|
||||||
if BaseKey, _ = base64.RawURLEncoding.DecodeString(raw); len(BaseKey) == 32 {
|
return nil
|
||||||
return
|
}
|
||||||
}
|
|
||||||
panic(platform.XUDPBaseKey + ": invalid value (BaseKey must be 32 bytes): " + raw + " len " + strconv.Itoa(len(BaseKey)))
|
func ensureBaseKey() []byte {
|
||||||
}
|
if key := baseKey.Load(); key != nil {
|
||||||
}()
|
return key.([]byte)
|
||||||
|
}
|
||||||
|
key := make([]byte, 32)
|
||||||
|
if _, err := rand.Read(key); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
baseKey.Store(key)
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
platform.RegisterEnvReload(reloadEnvSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGlobalID(ctx context.Context) (globalID [8]byte) {
|
func GetGlobalID(ctx context.Context) (globalID [8]byte) {
|
||||||
@@ -54,10 +68,10 @@ func GetGlobalID(ctx context.Context) (globalID [8]byte) {
|
|||||||
}
|
}
|
||||||
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.Network == net.Network_UDP &&
|
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.Network == net.Network_UDP &&
|
||||||
(inbound.Name == "dokodemo-door" || inbound.Name == "socks" || inbound.Name == "shadowsocks" || inbound.Name == "tun") {
|
(inbound.Name == "dokodemo-door" || inbound.Name == "socks" || inbound.Name == "shadowsocks" || inbound.Name == "tun") {
|
||||||
h := blake3.New(8, BaseKey)
|
h := blake3.New(8, ensureBaseKey())
|
||||||
h.Write([]byte(inbound.Source.String()))
|
h.Write([]byte(inbound.Source.String()))
|
||||||
copy(globalID[:], h.Sum(nil))
|
copy(globalID[:], h.Sum(nil))
|
||||||
if Show {
|
if Show.Load() {
|
||||||
errors.LogInfo(ctx, fmt.Sprintf("XUDP inbound.Source.String(): %v\tglobalID: %v\n", inbound.Source.String(), globalID))
|
errors.LogInfo(ctx, fmt.Sprintf("XUDP inbound.Source.String(): %v\tglobalID: %v\n", inbound.Source.String(), globalID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -125,7 +125,7 @@ func LoadConfig(formatName string, input interface{}) (*Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if f == "" {
|
if f == "" {
|
||||||
return nil, errors.New("Failed to get format of ", file).AtWarning()
|
return nil, errors.New("Failed to get format of ", file)
|
||||||
}
|
}
|
||||||
|
|
||||||
if f == "protobuf" {
|
if f == "protobuf" {
|
||||||
@@ -142,7 +142,7 @@ func LoadConfig(formatName string, input interface{}) (*Config, error) {
|
|||||||
if len(v) == 1 {
|
if len(v) == 1 {
|
||||||
return configLoaderByName["protobuf"].Loader(v)
|
return configLoaderByName["protobuf"].Loader(v)
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("Only one protobuf config file is allowed").AtWarning()
|
return nil, errors.New("Only one protobuf config file is allowed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,11 +152,11 @@ func LoadConfig(formatName string, input interface{}) (*Config, error) {
|
|||||||
if f, found := configLoaderByName[formatName]; found {
|
if f, found := configLoaderByName[formatName]; found {
|
||||||
return f.Loader(v)
|
return f.Loader(v)
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("Unable to load config in", formatName).AtWarning()
|
return nil, errors.New("Unable to load config in", formatName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("Unable to load config").AtWarning()
|
return nil, errors.New("Unable to load config")
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadProtobufConfig(data []byte) (*Config, error) {
|
func loadProtobufConfig(data []byte) (*Config, error) {
|
||||||
|
|||||||
+2
-2
@@ -19,8 +19,8 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
Version_x byte = 26
|
Version_x byte = 26
|
||||||
Version_y byte = 6
|
Version_y byte = 9
|
||||||
Version_z byte = 27
|
Version_z byte = 9
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -187,6 +187,9 @@ func NewWithContext(ctx context.Context, config *Config) (*Instance, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initInstanceWithConfig(config *Config, server *Instance) (bool, error) {
|
func initInstanceWithConfig(config *Config, server *Instance) (bool, error) {
|
||||||
|
if err := platform.ReloadEnvSettings(); err != nil {
|
||||||
|
return true, errors.New("failed to reload environment settings").Base(err)
|
||||||
|
}
|
||||||
server.ctx = context.WithValue(server.ctx, "cone",
|
server.ctx = context.WithValue(server.ctx, "cone",
|
||||||
platform.NewEnvFlag(platform.UseCone).GetValue(func() string { return "" }) != "true")
|
platform.NewEnvFlag(platform.UseCone).GetValue(func() string { return "" }) != "true")
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type FakeDNSEngine interface {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
FakeIPv4Pool = "198.18.0.0/15"
|
FakeIPv4Pool = "198.18.0.0/15"
|
||||||
FakeIPv6Pool = "fc00::/18"
|
FakeIPv6Pool = "2001:2::/48"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FakeDNSEngineRev0 interface {
|
type FakeDNSEngineRev0 interface {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package policy
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/platform"
|
"github.com/xtls/xray-core/common/platform"
|
||||||
@@ -82,32 +83,41 @@ func ManagerType() interface{} {
|
|||||||
return (*Manager)(nil)
|
return (*Manager)(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultBufferSize int32
|
var defaultBufferSize atomic.Int32
|
||||||
|
|
||||||
func init() {
|
func reloadEnvSettings() error {
|
||||||
|
defaultBufferSize.Store(readDefaultBufferSize())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readDefaultBufferSize() int32 {
|
||||||
const defaultValue = -17
|
const defaultValue = -17
|
||||||
size := platform.NewEnvFlag(platform.BufferSize).GetValueAsInt(defaultValue)
|
size := platform.NewEnvFlag(platform.BufferSize).GetValueAsInt(defaultValue)
|
||||||
|
|
||||||
switch size {
|
switch size {
|
||||||
case 0:
|
case 0:
|
||||||
defaultBufferSize = -1 // For pipe to use unlimited size
|
return -1 // For pipe to use unlimited size
|
||||||
case defaultValue: // Env flag not defined. Use default values per CPU-arch.
|
case defaultValue: // Env flag not defined. Use default values per CPU-arch.
|
||||||
switch runtime.GOARCH {
|
switch runtime.GOARCH {
|
||||||
case "arm", "mips", "mipsle":
|
case "arm", "mips", "mipsle":
|
||||||
defaultBufferSize = 0
|
return 0
|
||||||
case "arm64", "mips64", "mips64le":
|
case "arm64", "mips64", "mips64le":
|
||||||
defaultBufferSize = 4 * 1024 // 4k cache for low-end devices
|
return 4 * 1024 // 4k cache for low-end devices
|
||||||
default:
|
default:
|
||||||
defaultBufferSize = 512 * 1024
|
return 512 * 1024
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
defaultBufferSize = int32(size) * 1024 * 1024
|
return int32(size) * 1024 * 1024
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
platform.RegisterEnvReload(reloadEnvSettings)
|
||||||
|
}
|
||||||
|
|
||||||
func defaultBufferPolicy() Buffer {
|
func defaultBufferPolicy() Buffer {
|
||||||
return Buffer{
|
return Buffer{
|
||||||
PerConnection: defaultBufferSize,
|
PerConnection: defaultBufferSize.Load(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+21
-30
@@ -81,6 +81,8 @@ type Manager interface {
|
|||||||
|
|
||||||
// RegisterCounter registers a new counter to the manager. The identifier string must not be empty, and unique among other counters.
|
// RegisterCounter registers a new counter to the manager. The identifier string must not be empty, and unique among other counters.
|
||||||
RegisterCounter(string) (Counter, error)
|
RegisterCounter(string) (Counter, error)
|
||||||
|
// GetOrRegisterCounter returns the counter by its identifier, atomically creating and registering it if absent.
|
||||||
|
GetOrRegisterCounter(string) (Counter, error)
|
||||||
// UnregisterCounter unregisters a counter from the manager by its identifier.
|
// UnregisterCounter unregisters a counter from the manager by its identifier.
|
||||||
UnregisterCounter(string) error
|
UnregisterCounter(string) error
|
||||||
// GetCounter returns a counter by its identifier.
|
// GetCounter returns a counter by its identifier.
|
||||||
@@ -91,6 +93,8 @@ type Manager interface {
|
|||||||
|
|
||||||
// RegisterOnlineMap registers a new OnlineMap to the manager. The identifier string must not be empty, and unique among other OnlineMaps.
|
// RegisterOnlineMap registers a new OnlineMap to the manager. The identifier string must not be empty, and unique among other OnlineMaps.
|
||||||
RegisterOnlineMap(string) (OnlineMap, error)
|
RegisterOnlineMap(string) (OnlineMap, error)
|
||||||
|
// GetOrRegisterOnlineMap returns the OnlineMap by its identifier, atomically creating and registering it if absent.
|
||||||
|
GetOrRegisterOnlineMap(string) (OnlineMap, error)
|
||||||
// UnregisterOnlineMap unregisters an OnlineMap from the manager by its identifier.
|
// UnregisterOnlineMap unregisters an OnlineMap from the manager by its identifier.
|
||||||
UnregisterOnlineMap(string) error
|
UnregisterOnlineMap(string) error
|
||||||
// GetOnlineMap returns an OnlineMap by its identifier.
|
// GetOnlineMap returns an OnlineMap by its identifier.
|
||||||
@@ -101,6 +105,8 @@ type Manager interface {
|
|||||||
|
|
||||||
// RegisterChannel registers a new channel to the manager. The identifier string must not be empty, and unique among other channels.
|
// RegisterChannel registers a new channel to the manager. The identifier string must not be empty, and unique among other channels.
|
||||||
RegisterChannel(string) (Channel, error)
|
RegisterChannel(string) (Channel, error)
|
||||||
|
// GetOrRegisterChannel returns the channel by its identifier, atomically creating and registering it if absent.
|
||||||
|
GetOrRegisterChannel(string) (Channel, error)
|
||||||
// UnregisterChannel unregisters a channel from the manager by its identifier.
|
// UnregisterChannel unregisters a channel from the manager by its identifier.
|
||||||
UnregisterChannel(string) error
|
UnregisterChannel(string) error
|
||||||
// GetChannel returns a channel by its identifier.
|
// GetChannel returns a channel by its identifier.
|
||||||
@@ -110,36 +116,6 @@ type Manager interface {
|
|||||||
GetAllOnlineUsers() []string
|
GetAllOnlineUsers() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrRegisterCounter tries to get the StatCounter first. If not exist, it then tries to create a new counter.
|
|
||||||
func GetOrRegisterCounter(m Manager, name string) (Counter, error) {
|
|
||||||
counter := m.GetCounter(name)
|
|
||||||
if counter != nil {
|
|
||||||
return counter, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.RegisterCounter(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOrRegisterOnlineMap tries to get the OnlineMap first. If not exist, it then tries to create a new OnlineMap.
|
|
||||||
func GetOrRegisterOnlineMap(m Manager, name string) (OnlineMap, error) {
|
|
||||||
onlineMap := m.GetOnlineMap(name)
|
|
||||||
if onlineMap != nil {
|
|
||||||
return onlineMap, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.RegisterOnlineMap(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOrRegisterChannel tries to get the StatChannel first. If not exist, it then tries to create a new channel.
|
|
||||||
func GetOrRegisterChannel(m Manager, name string) (Channel, error) {
|
|
||||||
channel := m.GetChannel(name)
|
|
||||||
if channel != nil {
|
|
||||||
return channel, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.RegisterChannel(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ManagerType returns the type of Manager interface. Can be used to implement common.HasType.
|
// ManagerType returns the type of Manager interface. Can be used to implement common.HasType.
|
||||||
//
|
//
|
||||||
// xray:api:stable
|
// xray:api:stable
|
||||||
@@ -160,6 +136,11 @@ func (NoopManager) RegisterCounter(string) (Counter, error) {
|
|||||||
return nil, errors.New("not implemented")
|
return nil, errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetOrRegisterCounter implements Manager.
|
||||||
|
func (NoopManager) GetOrRegisterCounter(string) (Counter, error) {
|
||||||
|
return nil, errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
// UnregisterCounter implements Manager.
|
// UnregisterCounter implements Manager.
|
||||||
func (NoopManager) UnregisterCounter(string) error {
|
func (NoopManager) UnregisterCounter(string) error {
|
||||||
return nil
|
return nil
|
||||||
@@ -178,6 +159,11 @@ func (NoopManager) RegisterOnlineMap(string) (OnlineMap, error) {
|
|||||||
return nil, errors.New("not implemented")
|
return nil, errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetOrRegisterOnlineMap implements Manager.
|
||||||
|
func (NoopManager) GetOrRegisterOnlineMap(string) (OnlineMap, error) {
|
||||||
|
return nil, errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
// UnregisterOnlineMap implements Manager.
|
// UnregisterOnlineMap implements Manager.
|
||||||
func (NoopManager) UnregisterOnlineMap(string) error {
|
func (NoopManager) UnregisterOnlineMap(string) error {
|
||||||
return nil
|
return nil
|
||||||
@@ -196,6 +182,11 @@ func (NoopManager) RegisterChannel(string) (Channel, error) {
|
|||||||
return nil, errors.New("not implemented")
|
return nil, errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetOrRegisterChannel implements Manager.
|
||||||
|
func (NoopManager) GetOrRegisterChannel(string) (Channel, error) {
|
||||||
|
return nil, errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
// UnregisterChannel implements Manager.
|
// UnregisterChannel implements Manager.
|
||||||
func (NoopManager) UnregisterChannel(string) error {
|
func (NoopManager) UnregisterChannel(string) error {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,62 +1,63 @@
|
|||||||
module github.com/xtls/xray-core
|
module github.com/xtls/xray-core
|
||||||
|
|
||||||
go 1.26
|
go 1.27
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716
|
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e
|
||||||
github.com/cloudflare/circl v1.6.4
|
github.com/cloudflare/circl v1.6.5
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
|
||||||
github.com/golang/mock v1.7.0-rc.1
|
github.com/golang/mock v1.7.0-rc.1
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/klauspost/cpuid/v2 v2.4.0
|
github.com/klauspost/cpuid/v2 v2.4.0
|
||||||
github.com/miekg/dns v1.1.72
|
github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138
|
||||||
|
github.com/miekg/dns v1.1.73
|
||||||
github.com/pelletier/go-toml v1.9.5
|
github.com/pelletier/go-toml v1.9.5
|
||||||
github.com/pion/stun/v3 v3.1.6
|
github.com/pion/stun/v3 v3.1.7
|
||||||
github.com/pires/go-proxyproto v0.14.0
|
github.com/pires/go-proxyproto v0.15.0
|
||||||
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
|
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.1
|
||||||
github.com/sagernet/sing v0.5.1
|
github.com/stretchr/testify v1.12.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/vishvananda/netlink v1.3.1
|
||||||
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f
|
github.com/xtls/reality v0.0.0-20260908062103-8cdf7bf9c7f0
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/crypto v0.53.0
|
golang.org/x/crypto v0.57.0
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||||
golang.org/x/net v0.56.0
|
golang.org/x/net v0.59.0
|
||||||
golang.org/x/sync v0.21.0
|
golang.org/x/sync v0.23.0
|
||||||
golang.org/x/sys v0.46.0
|
golang.org/x/sys v0.48.0
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
||||||
golang.zx2c4.com/wireguard/windows v1.0.1
|
golang.zx2c4.com/wireguard/windows v1.1.1
|
||||||
google.golang.org/grpc v1.82.0
|
google.golang.org/grpc v1.84.0
|
||||||
google.golang.org/protobuf v1.36.11
|
google.golang.org/protobuf v1.36.12
|
||||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
|
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
|
||||||
h12.io/socks v1.0.3
|
h12.io/socks v1.0.3
|
||||||
lukechampine.com/blake3 v1.4.1
|
lukechampine.com/blake3 v1.4.1
|
||||||
|
mvdan.cc/gofumpt v0.12.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/google/btree v1.1.2 // indirect
|
github.com/google/btree v1.1.2 // indirect
|
||||||
|
github.com/google/gopacket v1.1.19 // indirect
|
||||||
|
github.com/huin/goupnp v1.2.0 // indirect
|
||||||
|
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
|
||||||
github.com/juju/ratelimit v1.0.2 // indirect
|
github.com/juju/ratelimit v1.0.2 // indirect
|
||||||
github.com/klauspost/compress v1.17.4 // indirect
|
github.com/klauspost/compress v1.17.4 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/koron/go-ssdp v0.0.4 // indirect
|
||||||
github.com/pion/dtls/v3 v3.1.4 // indirect
|
github.com/libp2p/go-netroute v0.2.1 // indirect
|
||||||
|
github.com/pion/dtls/v3 v3.1.5 // indirect
|
||||||
github.com/pion/logging v0.2.4 // indirect
|
github.com/pion/logging v0.2.4 // indirect
|
||||||
github.com/pion/transport/v4 v4.0.2 // indirect
|
github.com/pion/transport/v4 v4.1.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/quic-go/qpack v0.6.0 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/vishvananda/netns v0.0.5 // indirect
|
github.com/vishvananda/netns v0.0.5 // indirect
|
||||||
github.com/wlynxg/anet v0.0.5 // indirect
|
github.com/wlynxg/anet v0.0.5 // indirect
|
||||||
golang.org/x/mod v0.36.0 // indirect
|
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||||
golang.org/x/text v0.38.0 // indirect
|
golang.org/x/text v0.42.0 // indirect
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
golang.org/x/tools v0.45.0 // indirect
|
golang.org/x/tools v0.49.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,20 +1,13 @@
|
|||||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
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.61.1-0.20260806010916-184d081eef3e h1:5mgtR5gwIgBKMiGI1QdXldZZ+SNor06Nbu1wCBulQBg=
|
||||||
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=
|
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e/go.mod h1:x7qxEvX6MCVtDuBKHj3E+88+BtrbEMuAL5qGUKItjW8=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cloudflare/circl v1.6.5 h1:O64F26HEqNhznd/hrC5KZXVKYuKM2rx4deZDTc4ihQA=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cloudflare/circl v1.6.5/go.mod h1:h5LNyxAc5nTue9DS5jT+48en2PSDYt3zdGnz5OstK6c=
|
||||||
github.com/cloudflare/circl v1.6.4 h1:pOXuDTCEYyzydgUpQ0CQz3LsinKjiSk6nNP5Lt5K64U=
|
|
||||||
github.com/cloudflare/circl v1.6.4/go.mod h1:YxarevkLlbaHuWsxG6vmYNWBEsSp4pnp7j+4VljMavY=
|
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
github.com/go-quicktest/qt v1.102.0 h1:HSQxCeh5YZH3EL3W39ixjtyaEhcWSXQHtHnMBzSs474=
|
||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-quicktest/qt v1.102.0/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
|
||||||
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
|
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
|
||||||
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
|
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
@@ -23,97 +16,95 @@ github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
|||||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||||
|
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364 h1:5XxdakFhqd9dnXoAZy1Mb2R/DZ6D1e+0bGC/JhucGYI=
|
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364 h1:5XxdakFhqd9dnXoAZy1Mb2R/DZ6D1e+0bGC/JhucGYI=
|
||||||
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c=
|
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c=
|
||||||
|
github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY=
|
||||||
|
github.com/huin/goupnp v1.2.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
|
||||||
|
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
|
||||||
|
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
||||||
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
|
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
|
||||||
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
|
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
|
||||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||||
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
||||||
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
||||||
|
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
|
||||||
|
github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138 h1:YohuNPT/1k3VcThCQlBZ43PCPWPfMRS1zcxWBF2SLK8=
|
||||||
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138/go.mod h1:TXQg5tfSy+bUjnhT5728j5j/MBj7keIYqqZ1+8k/ui8=
|
||||||
|
github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU=
|
||||||
|
github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ=
|
||||||
|
github.com/miekg/dns v1.1.73 h1:uhT8nJxmTrPJYClxVxTCX+CVn6qnzSiybRk72Z6DgrE=
|
||||||
|
github.com/miekg/dns v1.1.73/go.mod h1:RW2Obtfd5NZHvOFe3zYG0W8koWOQtAzyHaLo8vASBuQ=
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
||||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
||||||
github.com/pion/dtls/v3 v3.1.4 h1:QhvtMflMfu9Kf0RcDC5BJBle4caPskByrKQR6uuYqpY=
|
github.com/pion/dtls/v3 v3.1.5 h1:9xJtVsHwMYeSjPp5Hh1FTis4DchnQWtnOa5o+6ygqfc=
|
||||||
github.com/pion/dtls/v3 v3.1.4/go.mod h1:cr/qotLISUw/9C1m83ZPNZtj9WnXkYLpfCptPqbkInc=
|
github.com/pion/dtls/v3 v3.1.5/go.mod h1:gz1K4jg6c+fq86oQMH4pilpCEOEPwmEr2jY+VcF/mkU=
|
||||||
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
|
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/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
|
||||||
github.com/pion/stun/v3 v3.1.6 h1:WnhsD0eHCiwCfKNkVx0VJJwr2Y3eV4Ueih3KJ+dfZy8=
|
github.com/pion/stun/v3 v3.1.7 h1:uRXMTlGLf89WgItGNyZ6aR5jMTX0NBbybXADpQCzn+E=
|
||||||
github.com/pion/stun/v3 v3.1.6/go.mod h1:zRUghXSQU32Lx5orJsz3uYMkIihweXb3mu5gIns02fs=
|
github.com/pion/stun/v3 v3.1.7/go.mod h1:Nq77RW4aRrSNrltf2ksUJLjxWeipj4lnlgdsYIxC8g8=
|
||||||
github.com/pion/transport/v4 v4.0.2 h1:ifYlPqNwsy6aKQ9y8yzxXlHae5431ZrH2avkD/Rn6Tk=
|
github.com/pion/transport/v4 v4.1.0 h1:8S+nF2reM2cJuqC6g78OVy2BBgmbdns+acx3jA97BvQ=
|
||||||
github.com/pion/transport/v4 v4.0.2/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM=
|
github.com/pion/transport/v4 v4.1.0/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM=
|
||||||
github.com/pires/go-proxyproto v0.14.0 h1:2vIGIfVG8eVRsKF0xukEoeT5RWhDXxBU0uv6smLOKdI=
|
github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI=
|
||||||
github.com/pires/go-proxyproto v0.14.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24=
|
github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
|
||||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
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/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 h1:er2acxbi3N1nvEq6HXHUAR1nTWEJmQfqiGR8EVT9rfs=
|
||||||
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
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 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.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
|
||||||
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
|
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
||||||
github.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
|
||||||
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
||||||
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
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 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
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 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
||||||
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
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-20260908062103-8cdf7bf9c7f0 h1:rb+fKQFhz+5I2PPuQsNYxI5mUU840XWYtRF0ZBjvkws=
|
||||||
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
|
github.com/xtls/reality v0.0.0-20260908062103-8cdf7bf9c7f0/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
|
||||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
go.opentelemetry.io/auto/sdk v1.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.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
|
||||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
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/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||||
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
|
||||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues=
|
||||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
|
||||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -121,21 +112,22 @@ 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.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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
|
||||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
|
||||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
|
||||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
|
||||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -143,27 +135,27 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
|
|||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
||||||
golang.zx2c4.com/wireguard/windows v1.0.1 h1:eOxiDVbywPC+ZQqvdCK7x+ZwWXKbYv50TtH8ysFIbw8=
|
golang.zx2c4.com/wireguard/windows v1.1.1 h1:8/H97U1v1PNDNcBsMZgU3KFuND9MQdTsU2NOwmCXArE=
|
||||||
golang.zx2c4.com/wireguard/windows v1.0.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs=
|
golang.zx2c4.com/wireguard/windows v1.1.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 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 h1:qEHAMpSaUhtD0p3NbEEI83HwNGFxEwaSJ1G9PLnCBZE=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||||
google.golang.org/grpc v1.82.0 h1:vguDnZUPjE26w09A63VoxZPnvPjB5Riyc0mkXPFmAIU=
|
google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0=
|
||||||
google.golang.org/grpc v1.82.0/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
|
google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 h1:Lk6hARj5UPY47dBep70OD/TIMwikJ5fGUGX0Rm3Xigk=
|
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 h1:Lk6hARj5UPY47dBep70OD/TIMwikJ5fGUGX0Rm3Xigk=
|
||||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=
|
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=
|
||||||
h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
|
h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
|
||||||
h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
|
h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
|
||||||
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
||||||
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
|
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
|
||||||
|
mvdan.cc/gofumpt v0.12.0 h1:1Lbudkz2kpM9Cjz2pL4M19u7q+GaEhCTNf7N9mfpcho=
|
||||||
|
mvdan.cc/gofumpt v0.12.0/go.mod h1:SmBHHrljiZu/uoypeKup3rFzP6eoC9UwCp2iH5E3jZA=
|
||||||
|
|||||||
+20
-30
@@ -1,52 +1,42 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/base64"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
|
||||||
"github.com/xtls/xray-core/proxy/blackhole"
|
"github.com/xtls/xray-core/proxy/blackhole"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NoneResponse struct{}
|
type ResponseConfig struct {
|
||||||
|
Type string `json:"type"`
|
||||||
func (*NoneResponse) Build() (proto.Message, error) {
|
CustomResponseData string `json:"customResponseData"`
|
||||||
return new(blackhole.NoneResponse), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type HTTPResponse struct{}
|
|
||||||
|
|
||||||
func (*HTTPResponse) Build() (proto.Message, error) {
|
|
||||||
return new(blackhole.HTTPResponse), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type BlackholeConfig struct {
|
type BlackholeConfig struct {
|
||||||
Response json.RawMessage `json:"response"`
|
Response *ResponseConfig `json:"response"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *BlackholeConfig) Build() (proto.Message, error) {
|
func (v *BlackholeConfig) Build() (proto.Message, error) {
|
||||||
config := new(blackhole.Config)
|
config := new(blackhole.Config)
|
||||||
if v.Response != nil {
|
if v.Response != nil {
|
||||||
response, _, err := configLoader.Load(v.Response)
|
responseName := strings.ToLower(v.Response.Type)
|
||||||
if err != nil {
|
switch responseName {
|
||||||
return nil, errors.New("Config: Failed to parse Blackhole response config.").Base(err)
|
case "none", "":
|
||||||
|
config.Response = &blackhole.Response{Type: "none"}
|
||||||
|
case "http":
|
||||||
|
config.Response = &blackhole.Response{Type: "http"}
|
||||||
|
case "custom":
|
||||||
|
data, err := base64.StdEncoding.DecodeString(v.Response.CustomResponseData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to decode custom response data: " + err.Error())
|
||||||
|
}
|
||||||
|
config.Response = &blackhole.Response{Type: "custom", CustomResponseData: data}
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unknown blackhole response: " + responseName)
|
||||||
}
|
}
|
||||||
responseSettings, err := response.(Buildable).Build()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config.Response = serial.ToTypedMessage(responseSettings)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var configLoader = NewJSONConfigLoader(
|
|
||||||
ConfigCreatorCache{
|
|
||||||
"none": func() interface{} { return new(NoneResponse) },
|
|
||||||
"http": func() interface{} { return new(HTTPResponse) },
|
|
||||||
},
|
|
||||||
"type",
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package conf_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/serial"
|
|
||||||
. "github.com/xtls/xray-core/infra/conf"
|
. "github.com/xtls/xray-core/infra/conf"
|
||||||
"github.com/xtls/xray-core/proxy/blackhole"
|
"github.com/xtls/xray-core/proxy/blackhole"
|
||||||
)
|
)
|
||||||
@@ -22,7 +21,7 @@ func TestHTTPResponseJSON(t *testing.T) {
|
|||||||
}`,
|
}`,
|
||||||
Parser: loadJSON(creator),
|
Parser: loadJSON(creator),
|
||||||
Output: &blackhole.Config{
|
Output: &blackhole.Config{
|
||||||
Response: serial.ToTypedMessage(&blackhole.HTTPResponse{}),
|
Response: &blackhole.Response{Type: "http"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -32,3 +31,27 @@ func TestHTTPResponseJSON(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCustomResponseJSON(t *testing.T) {
|
||||||
|
creator := func() Buildable {
|
||||||
|
return new(BlackholeConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
runMultiTestCase(t, []TestCase{
|
||||||
|
{
|
||||||
|
Input: `{
|
||||||
|
"response": {
|
||||||
|
"type": "custom",
|
||||||
|
"customResponseData": "Y3VzdG9tIHJlc3BvbnNl"
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
Parser: loadJSON(creator),
|
||||||
|
Output: &blackhole.Config{
|
||||||
|
Response: &blackhole.Response{
|
||||||
|
Type: "custom",
|
||||||
|
CustomResponseData: []byte("custom response"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ func (v *Address) UnmarshalJSON(data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *Address) Build() *net.IPOrDomain {
|
func (v *Address) Build() *net.IPOrDomain {
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return net.NewIPOrDomain(v.Address)
|
return net.NewIPOrDomain(v.Address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRootEnvAppliesArbitraryValues(t *testing.T) {
|
||||||
|
const (
|
||||||
|
valueKey = "XRAY_TEST_CONFIG_ENV"
|
||||||
|
emptyKey = "XRAY_TEST_CONFIG_EMPTY"
|
||||||
|
)
|
||||||
|
t.Setenv(valueKey, "before")
|
||||||
|
t.Setenv(emptyKey, "before")
|
||||||
|
|
||||||
|
config := new(Config)
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"env": {
|
||||||
|
"XRAY_TEST_CONFIG_ENV": "configured",
|
||||||
|
"XRAY_TEST_CONFIG_EMPTY": ""
|
||||||
|
}
|
||||||
|
}`), config); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := config.Build(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := os.Getenv(valueKey); got != "configured" {
|
||||||
|
t.Fatalf("env %q = %q, want %q", valueKey, got, "configured")
|
||||||
|
}
|
||||||
|
if got := os.Getenv(emptyKey); got != "" {
|
||||||
|
t.Fatalf("env %q = %q, want empty", emptyKey, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvConfigOverride(t *testing.T) {
|
||||||
|
base := EnvConfig{
|
||||||
|
"ONE": "one",
|
||||||
|
"TWO": "old",
|
||||||
|
}
|
||||||
|
override := EnvConfig{
|
||||||
|
"TWO": "new",
|
||||||
|
"THREE": "three",
|
||||||
|
}
|
||||||
|
base.Override(override)
|
||||||
|
|
||||||
|
want := map[string]string{
|
||||||
|
"ONE": "one",
|
||||||
|
"TWO": "new",
|
||||||
|
"THREE": "three",
|
||||||
|
}
|
||||||
|
for key, value := range want {
|
||||||
|
if got := base[key]; got != value {
|
||||||
|
t.Fatalf("env %q = %q, want %q", key, got, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -97,7 +97,7 @@ func (v *HTTPClientConfig) Build() (proto.Message, error) {
|
|||||||
user.Email = v.Email
|
user.Email = v.Email
|
||||||
} else {
|
} else {
|
||||||
if err := json.Unmarshal(rawUser, user); err != nil {
|
if err := json.Unmarshal(rawUser, user); err != nil {
|
||||||
return nil, errors.New("failed to parse HTTP user").Base(err).AtError()
|
return nil, errors.New("failed to parse HTTP user").Base(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
account := new(HTTPAccount)
|
account := new(HTTPAccount)
|
||||||
@@ -106,7 +106,7 @@ func (v *HTTPClientConfig) Build() (proto.Message, error) {
|
|||||||
account.Password = v.Password
|
account.Password = v.Password
|
||||||
} else {
|
} else {
|
||||||
if err := json.Unmarshal(rawUser, account); err != nil {
|
if err := json.Unmarshal(rawUser, account); err != nil {
|
||||||
return nil, errors.New("failed to parse HTTP account").Base(err).AtError()
|
return nil, errors.New("failed to parse HTTP account").Base(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
user.Account = serial.ToTypedMessage(account.Build())
|
user.Account = serial.ToTypedMessage(account.Build())
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ func RegisterConfigureFilePostProcessingStage(name string, stage ConfigureFilePo
|
|||||||
func PostProcessConfigureFile(conf *Config) error {
|
func PostProcessConfigureFile(conf *Config) error {
|
||||||
for k, v := range configureFilePostProcessingStages {
|
for k, v := range configureFilePostProcessingStages {
|
||||||
if err := v.Process(conf); err != nil {
|
if err := v.Process(conf); err != nil {
|
||||||
return errors.New("Rejected by Postprocessing Stage ", k).AtError().Base(err)
|
return errors.New("Rejected by Postprocessing Stage ", k).Base(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type ConfigCreatorCache map[string]ConfigCreator
|
|||||||
|
|
||||||
func (v ConfigCreatorCache) RegisterCreator(id string, creator ConfigCreator) error {
|
func (v ConfigCreatorCache) RegisterCreator(id string, creator ConfigCreator) error {
|
||||||
if _, found := v[id]; found {
|
if _, found := v[id]; found {
|
||||||
return errors.New(id, " already registered.").AtError()
|
return errors.New(id, " already registered.")
|
||||||
}
|
}
|
||||||
|
|
||||||
v[id] = creator
|
v[id] = creator
|
||||||
@@ -61,7 +61,7 @@ func (v *JSONConfigLoader) Load(raw []byte) (interface{}, string, error) {
|
|||||||
}
|
}
|
||||||
rawID, found := obj[v.idKey]
|
rawID, found := obj[v.idKey]
|
||||||
if !found {
|
if !found {
|
||||||
return nil, "", errors.New(v.idKey, " not found in JSON context").AtError()
|
return nil, "", errors.New(v.idKey, " not found in JSON context")
|
||||||
}
|
}
|
||||||
var id string
|
var id string
|
||||||
if err := json.Unmarshal(rawID, &id); err != nil {
|
if err := json.Unmarshal(rawID, &id); err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
|
"github.com/xtls/xray-core/proxy/masque"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MasqueClientConfig struct {
|
||||||
|
Address *Address `json:"address"`
|
||||||
|
Port uint16 `json:"port"`
|
||||||
|
RemoteDNS []string `json:"remoteDNS"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MasqueClientConfig) Build() (proto.Message, error) {
|
||||||
|
if c.Address == nil {
|
||||||
|
return nil, errors.New(`MASQUE: "address" is not set`)
|
||||||
|
}
|
||||||
|
if c.Port == 0 {
|
||||||
|
return nil, errors.New(`MASQUE: "port" is not set`)
|
||||||
|
}
|
||||||
|
for _, s := range c.RemoteDNS {
|
||||||
|
if _, err := netip.ParseAddr(s); err != nil {
|
||||||
|
return nil, errors.New(`MASQUE: invalid "remoteDNS" `, s).Base(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &masque.ClientConfig{
|
||||||
|
Server: &protocol.ServerEndpoint{
|
||||||
|
Address: c.Address.Build(),
|
||||||
|
Port: uint32(c.Port),
|
||||||
|
},
|
||||||
|
RemoteDns: c.RemoteDNS,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package conf_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/xtls/xray-core/infra/conf"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/masque"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMasqueConfig(t *testing.T) {
|
||||||
|
creator := func() Buildable {
|
||||||
|
return new(MasqueConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
runMultiTestCase(t, []TestCase{
|
||||||
|
{
|
||||||
|
Input: `{}`,
|
||||||
|
Parser: loadJSON(creator),
|
||||||
|
Output: &masque.Config{Path: "/.well-known/masque/ip/*/*/"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: `{
|
||||||
|
"host": "example.com:8443",
|
||||||
|
"path": "/.well-known/masque/ip/{target}/{ipproto}/",
|
||||||
|
"headers": {"Authorization": "Basic dTpw"}
|
||||||
|
}`,
|
||||||
|
Parser: loadJSON(creator),
|
||||||
|
Output: &masque.Config{
|
||||||
|
Host: "example.com:8443",
|
||||||
|
Path: "/.well-known/masque/ip/*/*/",
|
||||||
|
Headers: map[string]string{"Authorization": "Basic dTpw"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: `{"path": "/masque/ip{?target,ipproto}"}`,
|
||||||
|
Parser: loadJSON(creator),
|
||||||
|
Output: &masque.Config{Path: "/masque/ip?target=*&ipproto=*"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, input := range []string{
|
||||||
|
`{"path": "/masque/{target}/{ipproto}/{dns}"}`,
|
||||||
|
`{"path": "masque"}`,
|
||||||
|
`{"host": "example.com/path"}`,
|
||||||
|
`{"headers": {"host": "example.com"}}`,
|
||||||
|
`{"headers": {"Capsule-Protocol": "?0"}}`,
|
||||||
|
`{"headers": {"X Token": "a"}}`,
|
||||||
|
`{"headers": {"X-Token": "a\r\nb"}}`,
|
||||||
|
} {
|
||||||
|
if _, err := loadJSON(creator)(input); err == nil {
|
||||||
|
t.Errorf("expected an error for %s", input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMasqueOutboundConfig(t *testing.T) {
|
||||||
|
build := func(s string) error {
|
||||||
|
c := new(OutboundDetourConfig)
|
||||||
|
if err := json.Unmarshal([]byte(s), c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err := c.Build()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := build(`{
|
||||||
|
"protocol": "masque",
|
||||||
|
"settings": {"address": "example.com", "port": 443},
|
||||||
|
"streamSettings": {"network": "masque", "security": "tls"},
|
||||||
|
"mux": {"enabled": false, "concurrency": -1}
|
||||||
|
}`); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
for _, input := range []string{
|
||||||
|
`{"protocol": "masque", "settings": {"address": "example.com"}, "streamSettings": {"network": "masque", "security": "tls"}}`,
|
||||||
|
`{"protocol": "masque", "settings": {"address": "example.com", "port": 443}, "streamSettings": {"network": "masque", "security": "tls"}, "mux": {"enabled": true}}`,
|
||||||
|
`{"protocol": "masque", "settings": {"address": "example.com", "port": 443}, "streamSettings": {"network": "masque", "security": "tls"}, "mux": {"enabled": true, "concurrency": -1}}`,
|
||||||
|
`{"protocol": "freedom", "streamSettings": {"network": "masque", "security": "tls"}}`,
|
||||||
|
} {
|
||||||
|
if err := build(input); err == nil {
|
||||||
|
t.Errorf("expected an error for %s", input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ func (o *ObservatoryConfig) Build() (proto.Message, error) {
|
|||||||
type BurstObservatoryConfig struct {
|
type BurstObservatoryConfig struct {
|
||||||
SubjectSelector []string `json:"subjectSelector"`
|
SubjectSelector []string `json:"subjectSelector"`
|
||||||
// health check settings
|
// health check settings
|
||||||
HealthCheck *healthCheckSettings `json:"pingConfig,omitempty"`
|
HealthCheck *HealthCheckSettings `json:"pingConfig,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b BurstObservatoryConfig) Build() (proto.Message, error) {
|
func (b BurstObservatoryConfig) Build() (proto.Message, error) {
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
|||||||
LocalIP *StringList `json:"localIP"`
|
LocalIP *StringList `json:"localIP"`
|
||||||
LocalPort *PortList `json:"localPort"`
|
LocalPort *PortList `json:"localPort"`
|
||||||
Process *StringList `json:"process"`
|
Process *StringList `json:"process"`
|
||||||
|
LocalOS *StringList `json:"localOS"`
|
||||||
Webhook *WebhookRuleConfig `json:"webhook"`
|
Webhook *WebhookRuleConfig `json:"webhook"`
|
||||||
}
|
}
|
||||||
rawFieldRule := new(RawFieldRule)
|
rawFieldRule := new(RawFieldRule)
|
||||||
@@ -261,6 +262,10 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
|||||||
rule.Process = *rawFieldRule.Process
|
rule.Process = *rawFieldRule.Process
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rawFieldRule.LocalOS != nil && len(*rawFieldRule.LocalOS) > 0 {
|
||||||
|
rule.LocalOs = *rawFieldRule.LocalOS
|
||||||
|
}
|
||||||
|
|
||||||
if rawFieldRule.Webhook != nil && rawFieldRule.Webhook.URL != "" {
|
if rawFieldRule.Webhook != nil && rawFieldRule.Webhook.URL != "" {
|
||||||
rule.Webhook = &router.WebhookConfig{
|
rule.Webhook = &router.WebhookConfig{
|
||||||
Url: rawFieldRule.Webhook.URL,
|
Url: rawFieldRule.Webhook.URL,
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ type strategyLeastLoadConfig struct {
|
|||||||
Tolerance float64 `json:"tolerance,omitempty"`
|
Tolerance float64 `json:"tolerance,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// healthCheckSettings holds settings for health Checker
|
// HealthCheckSettings holds settings for health Checker
|
||||||
type healthCheckSettings struct {
|
type HealthCheckSettings struct {
|
||||||
Destination string `json:"destination"`
|
Destination string `json:"destination"`
|
||||||
Connectivity string `json:"connectivity"`
|
Connectivity string `json:"connectivity"`
|
||||||
Interval duration.Duration `json:"interval"`
|
Interval duration.Duration `json:"interval"`
|
||||||
@@ -53,7 +53,7 @@ type healthCheckSettings struct {
|
|||||||
HttpMethod string `json:"httpMethod"`
|
HttpMethod string `json:"httpMethod"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h healthCheckSettings) Build() (proto.Message, error) {
|
func (h HealthCheckSettings) Build() (proto.Message, error) {
|
||||||
var httpMethod string
|
var httpMethod string
|
||||||
if h.HttpMethod == "" {
|
if h.HttpMethod == "" {
|
||||||
httpMethod = "HEAD"
|
httpMethod = "HEAD"
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func MergeConfigFromFiles(files []*core.ConfigSource) (string, error) {
|
|||||||
if j, ok := creflect.MarshalToJson(c, true); ok {
|
if j, ok := creflect.MarshalToJson(c, true); ok {
|
||||||
return j, nil
|
return j, nil
|
||||||
}
|
}
|
||||||
return "", errors.New("marshal to json failed.").AtError()
|
return "", errors.New("marshal to json failed.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeConfigs(files []*core.ConfigSource) (*conf.Config, error) {
|
func mergeConfigs(files []*core.ConfigSource) (*conf.Config, error) {
|
||||||
|
|||||||
+37
-56
@@ -3,8 +3,6 @@ package conf
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
|
|
||||||
C "github.com/sagernet/sing/common"
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
@@ -55,7 +53,7 @@ func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
|
|||||||
v.Users = v.Clients
|
v.Users = v.Clients
|
||||||
}
|
}
|
||||||
|
|
||||||
if C.Contains(shadowaead_2022.List, v.Cipher) {
|
if _, err := shadowsocks_2022.GetCipherMethod(v.Cipher); err == nil {
|
||||||
return buildShadowsocks2022(v)
|
return buildShadowsocks2022(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,12 +109,14 @@ func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildShadowsocks2022(v *ShadowsocksServerConfig) (proto.Message, error) {
|
func buildShadowsocks2022(v *ShadowsocksServerConfig) (proto.Message, error) {
|
||||||
|
v.Cipher = strings.ToLower(v.Cipher)
|
||||||
if len(v.Users) == 0 {
|
if len(v.Users) == 0 {
|
||||||
config := new(shadowsocks_2022.ServerConfig)
|
config := new(shadowsocks_2022.ServerConfig)
|
||||||
config.Method = v.Cipher
|
config.Method = v.Cipher
|
||||||
config.Key = v.Password
|
config.Key = v.Password
|
||||||
config.Network = v.NetworkList.Build()
|
config.Network = v.NetworkList.Build()
|
||||||
config.Email = v.Email
|
config.Email = v.Email
|
||||||
|
config.Level = int32(v.Level)
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,6 +171,7 @@ func buildShadowsocks2022(v *ShadowsocksServerConfig) (proto.Message, error) {
|
|||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
Address: user.Address.Build(),
|
Address: user.Address.Build(),
|
||||||
Port: uint32(user.Port),
|
Port: uint32(user.Port),
|
||||||
|
Level: int32(user.Level),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return config, nil
|
return config, nil
|
||||||
@@ -214,63 +215,43 @@ func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
|
|||||||
return nil, errors.New(`Shadowsocks settings: "servers" should have one and only one member. Multiple endpoints in "servers" should use multiple Shadowsocks outbounds and routing balancer instead`)
|
return nil, errors.New(`Shadowsocks settings: "servers" should have one and only one member. Multiple endpoints in "servers" should use multiple Shadowsocks outbounds and routing balancer instead`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(v.Servers) == 1 {
|
server := v.Servers[0]
|
||||||
server := v.Servers[0]
|
if server.Address == nil {
|
||||||
if C.Contains(shadowaead_2022.List, server.Cipher) {
|
return nil, errors.New("Shadowsocks server address is not set.")
|
||||||
if server.Address == nil {
|
}
|
||||||
return nil, errors.New("Shadowsocks server address is not set.")
|
if server.Port == 0 {
|
||||||
}
|
return nil, errors.New("Invalid Shadowsocks port.")
|
||||||
if server.Port == 0 {
|
}
|
||||||
return nil, errors.New("Invalid Shadowsocks port.")
|
if server.Password == "" {
|
||||||
}
|
return nil, errors.New("Shadowsocks password is not specified.")
|
||||||
if server.Password == "" {
|
|
||||||
return nil, errors.New("Shadowsocks password is not specified.")
|
|
||||||
}
|
|
||||||
|
|
||||||
config := new(shadowsocks_2022.ClientConfig)
|
|
||||||
config.Address = server.Address.Build()
|
|
||||||
config.Port = uint32(server.Port)
|
|
||||||
config.Method = server.Cipher
|
|
||||||
config.Key = server.Password
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := shadowsocks_2022.GetCipherMethod(server.Cipher); err == nil {
|
||||||
|
config := new(shadowsocks_2022.ClientConfig)
|
||||||
|
config.Address = server.Address.Build()
|
||||||
|
config.Port = uint32(server.Port)
|
||||||
|
config.Method = server.Cipher
|
||||||
|
config.Key = server.Password
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
config := new(shadowsocks.ClientConfig)
|
config := new(shadowsocks.ClientConfig)
|
||||||
for _, server := range v.Servers {
|
account := &shadowsocks.Account{
|
||||||
if C.Contains(shadowaead_2022.List, server.Cipher) {
|
Password: server.Password,
|
||||||
return nil, errors.New("Shadowsocks 2022 accept no multi servers")
|
|
||||||
}
|
|
||||||
if server.Address == nil {
|
|
||||||
return nil, errors.New("Shadowsocks server address is not set.")
|
|
||||||
}
|
|
||||||
if server.Port == 0 {
|
|
||||||
return nil, errors.New("Invalid Shadowsocks port.")
|
|
||||||
}
|
|
||||||
if server.Password == "" {
|
|
||||||
return nil, errors.New("Shadowsocks password is not specified.")
|
|
||||||
}
|
|
||||||
account := &shadowsocks.Account{
|
|
||||||
Password: server.Password,
|
|
||||||
}
|
|
||||||
account.CipherType = cipherFromString(server.Cipher)
|
|
||||||
if account.CipherType == shadowsocks.CipherType_UNKNOWN {
|
|
||||||
return nil, errors.New("unknown cipher method: ", server.Cipher)
|
|
||||||
}
|
|
||||||
|
|
||||||
ss := &protocol.ServerEndpoint{
|
|
||||||
Address: server.Address.Build(),
|
|
||||||
Port: uint32(server.Port),
|
|
||||||
User: &protocol.User{
|
|
||||||
Level: uint32(server.Level),
|
|
||||||
Email: server.Email,
|
|
||||||
Account: serial.ToTypedMessage(account),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Server = ss
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
account.CipherType = cipherFromString(server.Cipher)
|
||||||
|
if account.CipherType == shadowsocks.CipherType_UNKNOWN {
|
||||||
|
return nil, errors.New("unknown cipher method: ", server.Cipher)
|
||||||
|
}
|
||||||
|
ss := &protocol.ServerEndpoint{
|
||||||
|
Address: server.Address.Build(),
|
||||||
|
Port: uint32(server.Port),
|
||||||
|
User: &protocol.User{
|
||||||
|
Level: uint32(server.Level),
|
||||||
|
Email: server.Email,
|
||||||
|
Account: serial.ToTypedMessage(account),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config.Server = ss
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -44,7 +44,6 @@ func (v *SocksServerConfig) Build() (proto.Message, error) {
|
|||||||
case AuthMethodUserPass:
|
case AuthMethodUserPass:
|
||||||
config.AuthType = socks.AuthType_PASSWORD
|
config.AuthType = socks.AuthType_PASSWORD
|
||||||
default:
|
default:
|
||||||
// errors.New("unknown socks auth method: ", v.AuthMethod, ". Default to noauth.").AtWarning().WriteToLog()
|
|
||||||
config.AuthType = socks.AuthType_NO_AUTH
|
config.AuthType = socks.AuthType_NO_AUTH
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +114,7 @@ func (v *SocksClientConfig) Build() (proto.Message, error) {
|
|||||||
user.Email = v.Email
|
user.Email = v.Email
|
||||||
} else {
|
} else {
|
||||||
if err := json.Unmarshal(rawUser, user); err != nil {
|
if err := json.Unmarshal(rawUser, user); err != nil {
|
||||||
return nil, errors.New("failed to parse Socks user").Base(err).AtError()
|
return nil, errors.New("failed to parse Socks user").Base(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
account := new(SocksAccount)
|
account := new(SocksAccount)
|
||||||
@@ -124,7 +123,7 @@ func (v *SocksClientConfig) Build() (proto.Message, error) {
|
|||||||
account.Password = v.Password
|
account.Password = v.Password
|
||||||
} else {
|
} else {
|
||||||
if err := json.Unmarshal(rawUser, account); err != nil {
|
if err := json.Unmarshal(rawUser, account); err != nil {
|
||||||
return nil, errors.New("failed to parse socks account").Base(err).AtError()
|
return nil, errors.New("failed to parse socks account").Base(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
user.Account = serial.ToTypedMessage(account.Build())
|
user.Account = serial.ToTypedMessage(account.Build())
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
googleuuid "github.com/google/uuid"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/fragment"
|
"github.com/xtls/xray-core/transport/internet/finalmask/fragment"
|
||||||
@@ -20,8 +23,10 @@ import (
|
|||||||
"github.com/xtls/xray-core/transport/internet/finalmask/realm"
|
"github.com/xtls/xray-core/transport/internet/finalmask/realm"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/salamander"
|
"github.com/xtls/xray-core/transport/internet/finalmask/salamander"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/sudoku"
|
"github.com/xtls/xray-core/transport/internet/finalmask/sudoku"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/finalmask/udphop"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/xdns"
|
"github.com/xtls/xray-core/transport/internet/finalmask/xdns"
|
||||||
"github.com/xtls/xray-core/transport/internet/finalmask/xicmp"
|
"github.com/xtls/xray-core/transport/internet/finalmask/xicmp"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/finalmask/xmc"
|
||||||
"github.com/xtls/xray-core/transport/internet/tls"
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
@@ -67,6 +72,7 @@ var (
|
|||||||
"header-custom": func() interface{} { return new(HeaderCustomTCP) },
|
"header-custom": func() interface{} { return new(HeaderCustomTCP) },
|
||||||
"fragment": func() interface{} { return new(FragmentMask) },
|
"fragment": func() interface{} { return new(FragmentMask) },
|
||||||
"sudoku": func() interface{} { return new(Sudoku) },
|
"sudoku": func() interface{} { return new(Sudoku) },
|
||||||
|
"xmc": func() interface{} { return new(XMC) },
|
||||||
}, "type", "settings")
|
}, "type", "settings")
|
||||||
|
|
||||||
udpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
udpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||||
@@ -78,6 +84,7 @@ var (
|
|||||||
"xdns": func() interface{} { return new(Xdns) },
|
"xdns": func() interface{} { return new(Xdns) },
|
||||||
"xicmp": func() interface{} { return new(Xicmp) },
|
"xicmp": func() interface{} { return new(Xicmp) },
|
||||||
"realm": func() interface{} { return new(Realm) },
|
"realm": func() interface{} { return new(Realm) },
|
||||||
|
"udphop": func() interface{} { return new(UDPHop) },
|
||||||
}, "type", "settings")
|
}, "type", "settings")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -715,6 +722,81 @@ func (c *Xdns) Build() (proto.Message, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type XMC struct {
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
|
Profiles []XMCProfile `json:"profiles"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type XMCProfile struct {
|
||||||
|
// Resolve the UUID by username, then request the session profile with
|
||||||
|
// unsigned=false. Client and server must use the same signed profile.
|
||||||
|
Username string `json:"username"`
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
TexturesValue string `json:"texturesValue"`
|
||||||
|
TexturesSignature string `json:"texturesSignature"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var xmcUsernamePattern = regexp.MustCompile(`^[A-Za-z0-9_]{3,16}$`)
|
||||||
|
|
||||||
|
func (c *XMCProfile) Build() (*xmc.Profile, error) {
|
||||||
|
if !xmcUsernamePattern.MatchString(c.Username) {
|
||||||
|
return nil, fmt.Errorf("invalid minecraft profile username: %q", c.Username)
|
||||||
|
}
|
||||||
|
|
||||||
|
profileUUID, err := googleuuid.Parse(c.UUID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid minecraft profile UUID: %w", err)
|
||||||
|
}
|
||||||
|
if c.TexturesValue == "" || c.TexturesSignature == "" {
|
||||||
|
return nil, fmt.Errorf("incomplete minecraft profile textures")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &xmc.Profile{
|
||||||
|
Username: c.Username,
|
||||||
|
Uuid: append([]byte(nil), profileUUID[:]...),
|
||||||
|
TexturesValue: c.TexturesValue,
|
||||||
|
TexturesSignature: c.TexturesSignature,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *XMC) Build() (proto.Message, error) {
|
||||||
|
if len(c.Profiles) == 0 {
|
||||||
|
return nil, fmt.Errorf("minecraft profiles are required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Password == "" {
|
||||||
|
return nil, fmt.Errorf("empty password")
|
||||||
|
}
|
||||||
|
|
||||||
|
rsaPrivateKey, err := xmc.DeriveRSAKey(c.Password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("derive minecraft rsa key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rsaPublicKey, err := x509.MarshalPKIXPublicKey(&rsaPrivateKey.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("marshal minecraft rsa public key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
profiles := make([]*xmc.Profile, 0, len(c.Profiles))
|
||||||
|
for i := range c.Profiles {
|
||||||
|
profile, err := c.Profiles[i].Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("build minecraft profile %d: %w", i, err)
|
||||||
|
}
|
||||||
|
profiles = append(profiles, profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &xmc.Config{
|
||||||
|
Password: c.Password,
|
||||||
|
Hostname: c.Hostname,
|
||||||
|
RsaPrivateKey: x509.MarshalPKCS1PrivateKey(rsaPrivateKey),
|
||||||
|
RsaPublicKey: rsaPublicKey,
|
||||||
|
Profiles: profiles,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
type Xicmp struct {
|
type Xicmp struct {
|
||||||
DGRAM bool `json:"dgram"`
|
DGRAM bool `json:"dgram"`
|
||||||
IPs []string `json:"ips"`
|
IPs []string `json:"ips"`
|
||||||
@@ -736,9 +818,11 @@ func (c *Xicmp) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Realm struct {
|
type Realm struct {
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
StunServers []string `json:"stunServers"`
|
StunServers []string `json:"stunServers"`
|
||||||
TlsConfig *TLSConfig `json:"tlsConfig"`
|
TlsConfig *TLSConfig `json:"tlsConfig"`
|
||||||
|
IPMode string `json:"ipMode"`
|
||||||
|
PortMapping *realm.PortMapping `json:"portMapping"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Realm) Build() (proto.Message, error) {
|
func (c *Realm) Build() (proto.Message, error) {
|
||||||
@@ -818,6 +902,54 @@ func (c *Realm) Build() (proto.Message, error) {
|
|||||||
ID: id,
|
ID: id,
|
||||||
StunServers: stunServers,
|
StunServers: stunServers,
|
||||||
TlsConfig: tlsConfig,
|
TlsConfig: tlsConfig,
|
||||||
|
IPMode: strings.ToLower(c.IPMode),
|
||||||
|
PortMapping: c.PortMapping,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type UDPHop struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Interval Int32Range `json:"interval"`
|
||||||
|
RemoteIPs []string `json:"remoteIPs"`
|
||||||
|
RemotePorts PortList `json:"remotePorts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UDPHop) Build() (proto.Message, error) {
|
||||||
|
var local, remote, remoteOnce bool
|
||||||
|
for _, mode := range strings.Split(c.Mode, ",") {
|
||||||
|
switch strings.ToLower(mode) {
|
||||||
|
case "intervallocal":
|
||||||
|
local = true
|
||||||
|
case "intervalremote":
|
||||||
|
remote = true
|
||||||
|
case "perconnremote":
|
||||||
|
remoteOnce = true
|
||||||
|
default:
|
||||||
|
return nil, errors.New("invalid mode ", mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var remoteIPs []string
|
||||||
|
for _, ip := range c.RemoteIPs {
|
||||||
|
prefix, err := netip.ParsePrefix(ip)
|
||||||
|
if err == nil {
|
||||||
|
remoteIPs = append(remoteIPs, prefix.String())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
addr, err := netip.ParseAddr(ip)
|
||||||
|
if err == nil {
|
||||||
|
remoteIPs = append(remoteIPs, netip.PrefixFrom(addr, addr.BitLen()).String())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, errors.New("invalid ip ", ip)
|
||||||
|
}
|
||||||
|
return &udphop.Config{
|
||||||
|
Local: local,
|
||||||
|
Remote: remote,
|
||||||
|
RemoteOnce: remoteOnce,
|
||||||
|
IntervalMin: int64(c.Interval.From),
|
||||||
|
IntervalMax: int64(c.Interval.To),
|
||||||
|
RemoteIPs: remoteIPs,
|
||||||
|
RemotePorts: c.RemotePorts.Build().Ports(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -848,20 +980,23 @@ func (c *Mask) Build(tcp bool) (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type QuicParamsConfig struct {
|
type QuicParamsConfig struct {
|
||||||
Congestion string `json:"congestion"`
|
Congestion string `json:"congestion"`
|
||||||
Debug bool `json:"debug"`
|
Debug bool `json:"debug"`
|
||||||
BbrProfile string `json:"bbrProfile"`
|
BbrProfile string `json:"bbrProfile"`
|
||||||
BrutalUp Bandwidth `json:"brutalUp"`
|
BrutalUp Bandwidth `json:"brutalUp"`
|
||||||
BrutalDown Bandwidth `json:"brutalDown"`
|
BrutalDown Bandwidth `json:"brutalDown"`
|
||||||
UdpHop UdpHop `json:"udpHop"`
|
BrutalDisableLossCompensation bool `json:"brutalDisableLossCompensation"`
|
||||||
InitStreamReceiveWindow uint64 `json:"initStreamReceiveWindow"`
|
InitStreamReceiveWindow uint64 `json:"initStreamReceiveWindow"`
|
||||||
MaxStreamReceiveWindow uint64 `json:"maxStreamReceiveWindow"`
|
MaxStreamReceiveWindow uint64 `json:"maxStreamReceiveWindow"`
|
||||||
InitConnectionReceiveWindow uint64 `json:"initConnectionReceiveWindow"`
|
InitConnectionReceiveWindow uint64 `json:"initConnectionReceiveWindow"`
|
||||||
MaxConnectionReceiveWindow uint64 `json:"maxConnectionReceiveWindow"`
|
MaxConnectionReceiveWindow uint64 `json:"maxConnectionReceiveWindow"`
|
||||||
MaxIdleTimeout int64 `json:"maxIdleTimeout"`
|
MaxIdleTimeout int64 `json:"maxIdleTimeout"`
|
||||||
KeepAlivePeriod int64 `json:"keepAlivePeriod"`
|
KeepAlivePeriod int64 `json:"keepAlivePeriod"`
|
||||||
DisablePathMTUDiscovery bool `json:"disablePathMTUDiscovery"`
|
DisablePathMTUDiscovery bool `json:"disablePathMTUDiscovery"`
|
||||||
MaxIncomingStreams int64 `json:"maxIncomingStreams"`
|
DisableChromeParrot bool `json:"disableChromeParrot"`
|
||||||
|
DisableGSO bool `json:"disableGSO"`
|
||||||
|
MaxIncomingStreams int64 `json:"maxIncomingStreams"`
|
||||||
|
DisableStatelessReset bool `json:"disableStatelessReset"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FinalMask struct {
|
type FinalMask struct {
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/transport/internet/finalmask/xmc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestXMCBuildProfile(t *testing.T) {
|
||||||
|
built, err := (&XMC{
|
||||||
|
Password: "test-password",
|
||||||
|
Profiles: []XMCProfile{
|
||||||
|
{
|
||||||
|
Username: "TestUser",
|
||||||
|
UUID: "00112233-4455-6677-8899-aabbccddeeff",
|
||||||
|
TexturesValue: "textures-value",
|
||||||
|
TexturesSignature: "textures-signature",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).Build()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("build XMC config: %v", err)
|
||||||
|
}
|
||||||
|
config := built.(*xmc.Config)
|
||||||
|
if len(config.Profiles) != 1 || len(config.Profiles[0].Uuid) != 16 {
|
||||||
|
t.Fatalf("unexpected profiles: %+v", config.Profiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestXMCBuildRequiresProfile(t *testing.T) {
|
||||||
|
_, err := (&XMC{Password: "test-password"}).Build()
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "profiles are required") {
|
||||||
|
t.Fatalf("expected required profiles error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,10 @@ func (p TransportProtocol) Build() (string, error) {
|
|||||||
return "", errors.PrintRemovedFeatureError("QUIC transport (without web service, etc.)", "XHTTP stream-one H3")
|
return "", errors.PrintRemovedFeatureError("QUIC transport (without web service, etc.)", "XHTTP stream-one H3")
|
||||||
case "hysteria":
|
case "hysteria":
|
||||||
return "hysteria", nil
|
return "hysteria", nil
|
||||||
|
case "masque":
|
||||||
|
return "masque", nil
|
||||||
|
case "xdrive":
|
||||||
|
return "xdrive", nil
|
||||||
default:
|
default:
|
||||||
return "", errors.New("Config: unknown transport protocol: ", p)
|
return "", errors.New("Config: unknown transport protocol: ", p)
|
||||||
}
|
}
|
||||||
@@ -59,6 +63,8 @@ type StreamConfig struct {
|
|||||||
WSSettings *WebSocketConfig `json:"wsSettings"`
|
WSSettings *WebSocketConfig `json:"wsSettings"`
|
||||||
HTTPUPGRADESettings *HttpUpgradeConfig `json:"httpupgradeSettings"`
|
HTTPUPGRADESettings *HttpUpgradeConfig `json:"httpupgradeSettings"`
|
||||||
HysteriaSettings *HysteriaConfig `json:"hysteriaSettings"`
|
HysteriaSettings *HysteriaConfig `json:"hysteriaSettings"`
|
||||||
|
MASQUESettings *MasqueConfig `json:"masqueSettings"`
|
||||||
|
XDRIVESettings *XDriveConfig `json:"xdriveSettings"`
|
||||||
SocketSettings *SocketConfig `json:"sockopt"`
|
SocketSettings *SocketConfig `json:"sockopt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,6 +198,26 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
Settings: serial.ToTypedMessage(hs),
|
Settings: serial.ToTypedMessage(hs),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if c.MASQUESettings != nil {
|
||||||
|
ms, err := c.MASQUESettings.Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("Failed to build MASQUE config.").Base(err)
|
||||||
|
}
|
||||||
|
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
||||||
|
ProtocolName: "masque",
|
||||||
|
Settings: serial.ToTypedMessage(ms),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if c.XDRIVESettings != nil {
|
||||||
|
xs, err := c.XDRIVESettings.Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("Failed to build XDRIVE config.").Base(err)
|
||||||
|
}
|
||||||
|
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
||||||
|
ProtocolName: "xdrive",
|
||||||
|
Settings: serial.ToTypedMessage(xs),
|
||||||
|
})
|
||||||
|
}
|
||||||
if c.SocketSettings != nil {
|
if c.SocketSettings != nil {
|
||||||
ss, err := c.SocketSettings.Build()
|
ss, err := c.SocketSettings.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -253,10 +279,6 @@ 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")
|
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) {
|
|
||||||
return nil, errors.New("Interval must be at least 5")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.FinalMask.QuicParams.InitStreamReceiveWindow > 0 && c.FinalMask.QuicParams.InitStreamReceiveWindow < 16384 {
|
if c.FinalMask.QuicParams.InitStreamReceiveWindow > 0 && c.FinalMask.QuicParams.InitStreamReceiveWindow < 16384 {
|
||||||
return nil, errors.New("InitStreamReceiveWindow must be at least 16384")
|
return nil, errors.New("InitStreamReceiveWindow must be at least 16384")
|
||||||
}
|
}
|
||||||
@@ -285,44 +307,25 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config.QuicParams = &internet.QuicParams{
|
config.QuicParams = &internet.QuicParams{
|
||||||
Congestion: c.FinalMask.QuicParams.Congestion,
|
Congestion: c.FinalMask.QuicParams.Congestion,
|
||||||
BbrProfile: profile,
|
BbrProfile: profile,
|
||||||
BrutalUp: up,
|
BrutalUp: up,
|
||||||
BrutalDown: down,
|
BrutalDown: down,
|
||||||
UdpHop: &internet.UdpHop{
|
BrutalDisableLossCompensation: c.FinalMask.QuicParams.BrutalDisableLossCompensation,
|
||||||
Ports: c.FinalMask.QuicParams.UdpHop.PortList.Build().Ports(),
|
InitStreamReceiveWindow: c.FinalMask.QuicParams.InitStreamReceiveWindow,
|
||||||
IntervalMin: int64(c.FinalMask.QuicParams.UdpHop.Interval.From),
|
MaxStreamReceiveWindow: c.FinalMask.QuicParams.MaxStreamReceiveWindow,
|
||||||
IntervalMax: int64(c.FinalMask.QuicParams.UdpHop.Interval.To),
|
InitConnReceiveWindow: c.FinalMask.QuicParams.InitConnectionReceiveWindow,
|
||||||
},
|
MaxConnReceiveWindow: c.FinalMask.QuicParams.MaxConnectionReceiveWindow,
|
||||||
InitStreamReceiveWindow: c.FinalMask.QuicParams.InitStreamReceiveWindow,
|
MaxIdleTimeout: c.FinalMask.QuicParams.MaxIdleTimeout,
|
||||||
MaxStreamReceiveWindow: c.FinalMask.QuicParams.MaxStreamReceiveWindow,
|
KeepAlivePeriod: c.FinalMask.QuicParams.KeepAlivePeriod,
|
||||||
InitConnReceiveWindow: c.FinalMask.QuicParams.InitConnectionReceiveWindow,
|
DisablePathMtuDiscovery: c.FinalMask.QuicParams.DisablePathMTUDiscovery,
|
||||||
MaxConnReceiveWindow: c.FinalMask.QuicParams.MaxConnectionReceiveWindow,
|
DisableChromeParrot: c.FinalMask.QuicParams.DisableChromeParrot,
|
||||||
MaxIdleTimeout: c.FinalMask.QuicParams.MaxIdleTimeout,
|
DisableGSO: c.FinalMask.QuicParams.DisableGSO,
|
||||||
KeepAlivePeriod: c.FinalMask.QuicParams.KeepAlivePeriod,
|
MaxIncomingStreams: c.FinalMask.QuicParams.MaxIncomingStreams,
|
||||||
DisablePathMtuDiscovery: c.FinalMask.QuicParams.DisablePathMTUDiscovery,
|
DisableStatelessReset: c.FinalMask.QuicParams.DisableStatelessReset,
|
||||||
MaxIncomingStreams: c.FinalMask.QuicParams.MaxIncomingStreams,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyConfig struct {
|
|
||||||
Tag string `json:"tag"`
|
|
||||||
|
|
||||||
// TransportLayerProxy: For compatibility.
|
|
||||||
TransportLayerProxy bool `json:"transportLayer"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build implements Buildable.
|
|
||||||
func (v *ProxyConfig) Build() (*internet.ProxyConfig, error) {
|
|
||||||
if v.Tag == "" {
|
|
||||||
return nil, errors.New("Proxy tag is not set.")
|
|
||||||
}
|
|
||||||
return &internet.ProxyConfig{
|
|
||||||
Tag: v.Tag,
|
|
||||||
TransportLayerProxy: v.TransportLayerProxy,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|||||||
+104
-32
@@ -1,7 +1,6 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -21,9 +20,12 @@ import (
|
|||||||
"github.com/xtls/xray-core/transport/internet/httpupgrade"
|
"github.com/xtls/xray-core/transport/internet/httpupgrade"
|
||||||
"github.com/xtls/xray-core/transport/internet/hysteria"
|
"github.com/xtls/xray-core/transport/internet/hysteria"
|
||||||
"github.com/xtls/xray-core/transport/internet/kcp"
|
"github.com/xtls/xray-core/transport/internet/kcp"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/masque"
|
||||||
"github.com/xtls/xray-core/transport/internet/splithttp"
|
"github.com/xtls/xray-core/transport/internet/splithttp"
|
||||||
"github.com/xtls/xray-core/transport/internet/tcp"
|
"github.com/xtls/xray-core/transport/internet/tcp"
|
||||||
"github.com/xtls/xray-core/transport/internet/websocket"
|
"github.com/xtls/xray-core/transport/internet/websocket"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/xdrive"
|
||||||
|
"golang.org/x/net/http/httpguts"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -122,7 +124,7 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {
|
|||||||
for _, key := range headerNames {
|
for _, key := range headerNames {
|
||||||
value := v.Headers[key]
|
value := v.Headers[key]
|
||||||
if value == nil {
|
if value == nil {
|
||||||
return nil, errors.New("empty HTTP header value: " + key).AtError()
|
return nil, errors.New("empty HTTP header value: " + key)
|
||||||
}
|
}
|
||||||
config.Header = append(config.Header, &http.Header{
|
config.Header = append(config.Header, &http.Header{
|
||||||
Name: key,
|
Name: key,
|
||||||
@@ -190,7 +192,7 @@ func (v *AuthenticatorResponse) Build() (*http.ResponseConfig, error) {
|
|||||||
for _, key := range headerNames {
|
for _, key := range headerNames {
|
||||||
value := v.Headers[key]
|
value := v.Headers[key]
|
||||||
if value == nil {
|
if value == nil {
|
||||||
return nil, errors.New("empty HTTP header value: " + key).AtError()
|
return nil, errors.New("empty HTTP header value: " + key)
|
||||||
}
|
}
|
||||||
config.Header = append(config.Header, &http.Header{
|
config.Header = append(config.Header, &http.Header{
|
||||||
Name: key,
|
Name: key,
|
||||||
@@ -240,11 +242,11 @@ func (c *TCPConfig) Build() (proto.Message, error) {
|
|||||||
if len(c.HeaderConfig) > 0 {
|
if len(c.HeaderConfig) > 0 {
|
||||||
headerConfig, _, err := tcpHeaderLoader.Load(c.HeaderConfig)
|
headerConfig, _, err := tcpHeaderLoader.Load(c.HeaderConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("invalid TCP header config").Base(err).AtError()
|
return nil, errors.New("invalid TCP header config").Base(err)
|
||||||
}
|
}
|
||||||
ts, err := headerConfig.(Buildable).Build()
|
ts, err := headerConfig.(Buildable).Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("invalid TCP header config").Base(err).AtError()
|
return nil, errors.New("invalid TCP header config").Base(err)
|
||||||
}
|
}
|
||||||
config.HeaderSettings = serial.ToTypedMessage(ts)
|
config.HeaderSettings = serial.ToTypedMessage(ts)
|
||||||
}
|
}
|
||||||
@@ -450,8 +452,8 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
|||||||
return nil, errors.New("maxConnections cannot be specified together with maxConcurrency")
|
return nil, errors.New("maxConnections cannot be specified together with maxConcurrency")
|
||||||
}
|
}
|
||||||
if c.Xmux == (XmuxConfig{}) {
|
if c.Xmux == (XmuxConfig{}) {
|
||||||
c.Xmux.MaxConnections.From = 6
|
c.Xmux.MaxConnections.From = 3
|
||||||
c.Xmux.MaxConnections.To = 6
|
c.Xmux.MaxConnections.To = 3
|
||||||
c.Xmux.HMaxRequestTimes.From = 600
|
c.Xmux.HMaxRequestTimes.From = 600
|
||||||
c.Xmux.HMaxRequestTimes.To = 900
|
c.Xmux.HMaxRequestTimes.To = 900
|
||||||
c.Xmux.HMaxReusableSecs.From = 1800
|
c.Xmux.HMaxReusableSecs.From = 1800
|
||||||
@@ -534,10 +536,6 @@ type KCPConfig struct {
|
|||||||
|
|
||||||
// Build implements Buildable.
|
// Build implements Buildable.
|
||||||
func (c *KCPConfig) Build() (proto.Message, error) {
|
func (c *KCPConfig) Build() (proto.Message, error) {
|
||||||
if c.HeaderConfig != nil || c.Seed != nil {
|
|
||||||
return nil, errors.PrintRemovedFeatureError("mkcp header & seed", "finalmask/udp header-* & mkcp-original & mkcp-aes128gcm")
|
|
||||||
}
|
|
||||||
|
|
||||||
config := common.Must2(internet.CreateTransportConfig(kcp.ProtocolName)).(*kcp.Config)
|
config := common.Must2(internet.CreateTransportConfig(kcp.ProtocolName)).(*kcp.Config)
|
||||||
|
|
||||||
if c.Mtu != nil {
|
if c.Mtu != nil {
|
||||||
@@ -560,16 +558,16 @@ func (c *KCPConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config.Mtu < 21 {
|
if config.Mtu < 21 {
|
||||||
return nil, errors.New("Mtu must be at least 21").AtError()
|
return nil, errors.New("MTU must be at least 21")
|
||||||
}
|
}
|
||||||
if config.Tti < 10 || config.Tti > 1000 {
|
if config.Tti < 10 || config.Tti > 1000 {
|
||||||
return nil, errors.New("invalid mKCP TTI: ", c.Tti).AtError()
|
return nil, errors.New("TTI must be between 10 and 1000")
|
||||||
}
|
}
|
||||||
if config.CwndMultiplier < 1 {
|
if config.CwndMultiplier < 1 {
|
||||||
return nil, errors.New("CwndMultiplier must be at least 1").AtError()
|
return nil, errors.New("CwndMultiplier must be at least 1")
|
||||||
}
|
}
|
||||||
if config.GetSendingBufferSize() == 0 {
|
if config.GetSendingBufferSize() == 0 {
|
||||||
return nil, errors.New("MaxSendingWindow must be >= Mtu").AtError()
|
return nil, errors.New("MaxSendingWindow must be at least ", config.Mtu)
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
@@ -739,11 +737,6 @@ func (b Bandwidth) Bps() (uint64, error) {
|
|||||||
return uint64(val*float64(mul)) / 8, nil
|
return uint64(val*float64(mul)) / 8, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type UdpHop struct {
|
|
||||||
PortList PortList `json:"ports"`
|
|
||||||
Interval Int32Range `json:"interval"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Masquerade struct {
|
type Masquerade struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
|
||||||
@@ -751,6 +744,7 @@ type Masquerade struct {
|
|||||||
|
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
RewriteHost bool `json:"rewriteHost"`
|
RewriteHost bool `json:"rewriteHost"`
|
||||||
|
XForwarded bool `json:"xForwarded"`
|
||||||
Insecure bool `json:"insecure"`
|
Insecure bool `json:"insecure"`
|
||||||
|
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
@@ -759,14 +753,8 @@ type Masquerade struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HysteriaConfig struct {
|
type HysteriaConfig struct {
|
||||||
Version int32 `json:"version"`
|
Version int32 `json:"version"`
|
||||||
Auth string `json:"auth"`
|
Auth string `json:"auth"`
|
||||||
|
|
||||||
Congestion *string `json:"congestion"`
|
|
||||||
Up *Bandwidth `json:"up"`
|
|
||||||
Down *Bandwidth `json:"down"`
|
|
||||||
UdpHop *UdpHop `json:"udphop"`
|
|
||||||
|
|
||||||
UdpIdleTimeout int64 `json:"udpIdleTimeout"`
|
UdpIdleTimeout int64 `json:"udpIdleTimeout"`
|
||||||
Masquerade Masquerade `json:"masquerade"`
|
Masquerade Masquerade `json:"masquerade"`
|
||||||
}
|
}
|
||||||
@@ -776,10 +764,6 @@ func (c *HysteriaConfig) Build() (proto.Message, error) {
|
|||||||
return nil, errors.New("version != 2")
|
return nil, errors.New("version != 2")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Congestion != nil || c.Up != nil || c.Down != nil || c.UdpHop != nil {
|
|
||||||
errors.LogWarning(context.Background(), "congestion & up & down & udphop move to finalmask/quicParams")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.UdpIdleTimeout != 0 && (c.UdpIdleTimeout < 2 || c.UdpIdleTimeout > 600) {
|
if c.UdpIdleTimeout != 0 && (c.UdpIdleTimeout < 2 || c.UdpIdleTimeout > 600) {
|
||||||
return nil, errors.New("UdpIdleTimeout must be between 2 and 600")
|
return nil, errors.New("UdpIdleTimeout must be between 2 and 600")
|
||||||
}
|
}
|
||||||
@@ -791,6 +775,7 @@ func (c *HysteriaConfig) Build() (proto.Message, error) {
|
|||||||
config.MasqFile = c.Masquerade.Dir
|
config.MasqFile = c.Masquerade.Dir
|
||||||
config.MasqUrl = c.Masquerade.Url
|
config.MasqUrl = c.Masquerade.Url
|
||||||
config.MasqUrlRewriteHost = c.Masquerade.RewriteHost
|
config.MasqUrlRewriteHost = c.Masquerade.RewriteHost
|
||||||
|
config.MasqUrlXForwarded = c.Masquerade.XForwarded
|
||||||
config.MasqUrlInsecure = c.Masquerade.Insecure
|
config.MasqUrlInsecure = c.Masquerade.Insecure
|
||||||
config.MasqString = c.Masquerade.Content
|
config.MasqString = c.Masquerade.Content
|
||||||
config.MasqStringHeaders = c.Masquerade.Headers
|
config.MasqStringHeaders = c.Masquerade.Headers
|
||||||
@@ -803,6 +788,46 @@ func (c *HysteriaConfig) Build() (proto.Message, error) {
|
|||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MasqueConfig struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Headers map[string]string `json:"headers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MasqueConfig) Build() (proto.Message, error) {
|
||||||
|
path := c.Path
|
||||||
|
if path == "" {
|
||||||
|
path = masque.DefaultPath
|
||||||
|
}
|
||||||
|
path = strings.NewReplacer(
|
||||||
|
"{target}", "*", "{ipproto}", "*",
|
||||||
|
"{?target,ipproto}", "?target=*&ipproto=*", "{?ipproto,target}", "?ipproto=*&target=*",
|
||||||
|
"{&target,ipproto}", "&target=*&ipproto=*", "{&ipproto,target}", "&ipproto=*&target=*",
|
||||||
|
).Replace(path)
|
||||||
|
if !strings.HasPrefix(path, "/") || strings.ContainsAny(path, "{}") {
|
||||||
|
return nil, errors.New(`invalid "path": `, path, `, only the variables {target} and {ipproto} are supported`)
|
||||||
|
}
|
||||||
|
if c.Host != "" {
|
||||||
|
if u, err := url.Parse("https://" + c.Host); err != nil || u.Host != c.Host {
|
||||||
|
return nil, errors.New(`invalid "host": `, c.Host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range c.Headers {
|
||||||
|
if !httpguts.ValidHeaderFieldName(k) || !httpguts.ValidHeaderFieldValue(v) {
|
||||||
|
return nil, errors.New(`invalid header in "headers": `, strconv.Quote(k))
|
||||||
|
}
|
||||||
|
switch strings.ToLower(k) {
|
||||||
|
case "host", "capsule-protocol":
|
||||||
|
return nil, errors.New(`"headers" can't contain "`, k, `"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &masque.Config{
|
||||||
|
Host: c.Host,
|
||||||
|
Path: path,
|
||||||
|
Headers: c.Headers,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func readFileOrString(f string, s []string) ([]byte, error) {
|
func readFileOrString(f string, s []string) ([]byte, error) {
|
||||||
if len(f) > 0 {
|
if len(f) > 0 {
|
||||||
return filesystem.ReadCert(f)
|
return filesystem.ReadCert(f)
|
||||||
@@ -812,3 +837,50 @@ func readFileOrString(f string, s []string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
return nil, errors.New("both file and bytes are empty.")
|
return nil, errors.New("both file and bytes are empty.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type XDriveConfig struct {
|
||||||
|
RemoteFolder string `json:"remoteFolder"`
|
||||||
|
Service string `json:"service"`
|
||||||
|
Secrets []string `json:"secrets"`
|
||||||
|
SegmentBytes uint32 `json:"segmentBytes"`
|
||||||
|
FlushIntervalMs uint32 `json:"flushIntervalMs"`
|
||||||
|
PollIntervalMs uint32 `json:"pollIntervalMs"`
|
||||||
|
MaxPollIntervalMs uint32 `json:"maxPollIntervalMs"`
|
||||||
|
SessionTTLSeconds uint32 `json:"sessionTtlSeconds"`
|
||||||
|
Concurrency uint32 `json:"concurrency"`
|
||||||
|
EagerWindowMs uint32 `json:"eagerWindowMs"`
|
||||||
|
HoleTimeoutMs uint32 `json:"holeTimeoutMs"`
|
||||||
|
Template json.RawMessage `json:"template"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build implements Buildable.
|
||||||
|
func (c *XDriveConfig) Build() (proto.Message, error) {
|
||||||
|
switch c.Service {
|
||||||
|
case "local":
|
||||||
|
case "Google Drive":
|
||||||
|
if len(c.Secrets) != 3 {
|
||||||
|
return nil, errors.New("Google Drive needs 3 secrets in order of ClientID, ClientSecret, RefreshToken")
|
||||||
|
}
|
||||||
|
case "template":
|
||||||
|
if len(c.Template) == 0 {
|
||||||
|
return nil, errors.New(`service "template" needs a "template" object`)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unsupported service")
|
||||||
|
}
|
||||||
|
config := &xdrive.Config{
|
||||||
|
RemoteFolder: c.RemoteFolder,
|
||||||
|
Service: c.Service,
|
||||||
|
Secrets: c.Secrets,
|
||||||
|
SegmentBytes: c.SegmentBytes,
|
||||||
|
FlushIntervalMs: c.FlushIntervalMs,
|
||||||
|
PollIntervalMs: c.PollIntervalMs,
|
||||||
|
MaxPollIntervalMs: c.MaxPollIntervalMs,
|
||||||
|
SessionTtlSeconds: c.SessionTTLSeconds,
|
||||||
|
Concurrency: c.Concurrency,
|
||||||
|
EagerWindowMs: c.EagerWindowMs,
|
||||||
|
HoleTimeoutMs: c.HoleTimeoutMs,
|
||||||
|
Template: string(c.Template),
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -113,6 +113,10 @@ func (c *REALITYConfig) Build() (proto.Message, error) {
|
|||||||
config.MinClientVer[i] = byte(u)
|
config.MinClientVer[i] = byte(u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// errors.LogWarning(context.Background(), `REALITY: Changing "minClientVer" will increase the likelihood of your server's IP being blocked by the GFW`)
|
||||||
|
} else {
|
||||||
|
// config.MinClientVer = []byte{26, 3, 27} // change it at your own risk: https://github.com/XTLS/Xray-core/commit/af7eb68028732a8ee3c0e5d6ab2b8a657bb2e770
|
||||||
|
// errors.LogWarning(context.Background(), `REALITY: The default minimal client version is Xray-core v26.3.27, other clients may be refused to connect`)
|
||||||
}
|
}
|
||||||
if c.MaxClientVer != "" {
|
if c.MaxClientVer != "" {
|
||||||
config.MaxClientVer = make([]byte, 3)
|
config.MaxClientVer = make([]byte, 3)
|
||||||
@@ -157,8 +161,10 @@ func (c *REALITYConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, sn := range config.ServerNames {
|
for _, sn := range config.ServerNames {
|
||||||
if strings.Contains(sn, "apple") || strings.Contains(sn, "icloud") {
|
sn = strings.ToLower(sn)
|
||||||
errors.LogWarning(context.Background(), `REALITY: Choosing apple, icloud, etc. as the target may get your IP blocked by the GFW`)
|
if strings.HasSuffix(sn, ".ru") || strings.HasSuffix(sn, ".ir") || strings.HasSuffix(sn, ".cn") ||
|
||||||
|
strings.Contains(sn, "apple") || strings.Contains(sn, "icloud") || strings.Contains(sn, "microsoft") {
|
||||||
|
errors.LogWarning(context.Background(), `REALITY: Choosing "`, sn, `" as the target will increase the likelihood of your server's IP being blocked by the GFW`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type CustomSockoptConfig struct {
|
type CustomSockoptConfig struct {
|
||||||
Syetem string `json:"system"`
|
System string `json:"system"`
|
||||||
Network string `json:"network"`
|
Network string `json:"network"`
|
||||||
Level string `json:"level"`
|
Level string `json:"level"`
|
||||||
Opt string `json:"opt"`
|
Opt string `json:"opt"`
|
||||||
@@ -124,7 +124,7 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
|
|||||||
|
|
||||||
for _, copt := range c.CustomSockopt {
|
for _, copt := range c.CustomSockopt {
|
||||||
customSockopt := &internet.CustomSockopt{
|
customSockopt := &internet.CustomSockopt{
|
||||||
System: copt.Syetem,
|
System: copt.System,
|
||||||
Network: copt.Network,
|
Network: copt.Network,
|
||||||
Level: copt.Level,
|
Level: copt.Level,
|
||||||
Opt: copt.Opt,
|
Opt: copt.Opt,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user