Compare commits

..
Author SHA1 Message Date
patternihaandClaude Opus 5.5 9638715047 TUN inbound's autoSystemWfpBlockLeak: A CIDR in front of any value instead of "ipv4:" and "ipv6:"; Ports alone for TCP and UDP
A CIDR in front of any value limits it to its addresses, an IPv6 one in
brackets, e.g. "1.1.1.0/24:53", "[2001:db8::/32]:tcp/853" or
"[::/0]:all": "0.0.0.0/0:" and "[::/0]:" take the place of "ipv4:" and
"ipv6:". Like autoSystemRoutingTable, it takes CIDRs only, a single
address as "8.8.8.8/32", and no GeoIP.

"<port>" or "<low>-<high>" alone stands for TCP and UDP, so "dns" is
"53", "853" and "windnsclient" now.

The filters follow:
- One filter per layer permits what goes through the TUN, instead of one
  for each value.
- "all" and "misconfigtun" outweigh the local network that "allexceptlan"
  leaves open, so with it, "192.168.1.5/32:all" still blocks that address.
- The DNS Client service's mDNS and LLMNR stay allowed on the local network
  only, and only while no other value blocks them.
- The warning about blocked DNS servers only names those a value blocks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 22:21:31 +03:30
patternihaandClaude Opus 5.5 f5cc086d6c TUN inbound: README.md says "ipv4:allexceptlan" includes "ipv4:dns" only
Add "dns" to block DNS over IPv6 as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 18:10:11 +03:30
patternihaandClaude Opus 5.5 6b45eadf45 TUN inbound: Recommend at least "dns" for autoSystemWfpBlockLeak in README.md, and shorten it
README.md now recommends at least "dns" to block DNS leaks, and lists the
values of autoSystemWfpBlockLeak more briefly, "dns" first. It also lists
FreeBSD among the systems, and mentions that on Linux, as on Android, the
TUN can be a file descriptor from xray.tun.fd (XRAY_TUN_FD).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 17:53:27 +03:30
patternihaandClaude Opus 5.5 c684201cd8 TUN inbound's autoSystemWfpBlockLeak: "ipv4:" or "ipv6:" in front of any value instead of "tcpv4" and the like
"ipv4:" or "ipv6:" in front of any value limits it to that IP version,
e.g. "ipv4:udp/53", "ipv6:all", "ipv4:dns" or "ipv6:misconfigtun".
"tcpv4", "udpv6", "icmpv4" and the like are gone. Values that only differ
in the IP version become one: "ipv4:udp/53" with "ipv6:udp/53" is
"udp/53".

"all" overrides "allexceptlan" per IP version, and "all" over one IP
version needs dns, as port 53 over one does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 17:52:23 +03:30
patternihaandClaude Opus 5.5 2218fec173 TUN inbound's autoSystemWfpBlockLeak: Add "tcpv4", "tcpv6", "udpv4", "udpv6", "icmpv4" and "icmpv6"
"v4" or "v6" after the protocol limits a value to one IP version, ports
written the same way, e.g. "tcpv4/853" or "udpv6/10-20"; "icmpv4" is ICMP
and "icmpv6" is ICMPv6. Their filters go to the ALE layers of that version
only. "tcp", "udp" and "icmp" keep covering both.

Anything covering TCP or UDP port 53 in either IP version, like
"udpv4/53", still needs dns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:09:55 +03:30
patternihaandClaude Opus 5.5 9ffa0f0552 TUN inbound's autoSystemWfpBlockLeak: Drop "/0"; Ports have to be 1-65535
"tcp/0" and "udp/0" no longer stand for all ports, which "tcp" and "udp"
already say: no syntactic sugar. Port 0 is rejected like any port outside
1-65535. Ranges keep being checked strictly: both ends within 1-65535, the
left one not greater than the right one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 12:49:56 +03:30
patternihaandClaude Opus 5.5 2933b0c8d5 TUN inbound: Don't warn that loopback DNS servers are blocked
With autoSystemWfpBlockLeak, loopback now always stays allowed, so a DNS
server in dns like 127.0.0.1, a local resolver, stays reachable although
it lies in neither gateway nor autoSystemRoutingTable. Leave loopback
servers out of the warning that queries to them cannot go through the TUN
and are blocked, and say so in the README.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 12:27:26 +03:30
patternihaandClaude Opus 5.5 bb4cd4b6d3 TUN inbound's autoSystemWfpBlockLeak: Add "allexceptlan"
"allexceptlan" is "all" but for the local network: traffic to and from
private (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7), link-local
(169.254.0.0/16, fe80::/10), multicast (224.0.0.0/4, ff00::/8) and broadcast
(255.255.255.255) addresses stays allowed, so that printers, the router's
settings page, or casting to a TV keep working while nothing else leaves
outside the TUN.

It includes "dns": a resolver on the local network, like the router's,
would forward the queries outside the TUN, so DNS stays blocked there. The
LAN permit sits below the other values, like the permit through the TUN, so
they still apply on the local network, e.g. "tcp/445" blocks Windows file
sharing with it. The DNS Client service keeps mDNS and LLMNR, to the local
network's addresses only. "all" overrides it.

The addresses are matched with FWP_V4_ADDR_MASK and FWP_V6_ADDR_MASK
conditions on the remote address, which match when any of them does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 11:50:18 +03:30
patternihaandClaude Opus 5.5 468f745fc2 TUN inbound's autoSystemWfpBlockLeak: Add "all", "tcp", "udp", "icmp" and port ranges; Always allow loopback, DHCP and IPv6 neighbor discovery
autoSystemWfpBlockLeak now also takes:

- "all": everything outside the TUN, IPv4 and IPv6, in both directions, so
  nothing but Xray leaves except through the TUN.
- "tcp", "udp": all ports of that protocol, also written "tcp/0", "udp/0".
- "tcp/<low>-<high>", "udp/<low>-<high>": a range of ports, matched with
  FWP_MATCH_RANGE, as two conditions on the same field would match when
  either does.
- "icmp": ICMP and ICMPv6, e.g. ping. It takes no port: in ICMP filters the
  local port holds the type, and "icmp/0" would read as the echo reply.

Values are normalized: "tcp/0" and "tcp/1-65535" become "tcp", and
"tcp/53-53" becomes "tcp/53".

Whatever is blocked, loopback, which cannot leave the system, and what
Windows itself needs on the local link now always stay allowed: DHCP, DHCPv6,
and IPv6 neighbor and multicast listener discovery. These used to be allowed
only along with "misconfigtun", so "udp" or "icmp" would otherwise have cut
DHCP or IPv6 neighbor discovery, and "tcp" any local proxy, Xray's own
inbounds included. Loopback being allowed means that with "dns", a local
resolver on 127.0.0.1:53 now works for other programs (its own queries to
port 53 or 853 outside the TUN stay blocked), and the DNS Client service
may use it.

"all" reuses the filters of "misconfigtun", which now also let everything
through the TUN, for both IP versions whatever the routes.

On other TUN options: keeping the system's DNS inside the TUN, with "all",
"dns", "windnsclient", or anything covering TCP or UDP port 53 ("udp",
"tcp/50-60", ...), needs dns, as Windows can no longer reach the DNS
servers of the other interfaces; the config is rejected otherwise. Like the
other values, the new ones need autoSystemRoutingTable. The filters do not
need autoOutboundsInterface, as Xray is allowed by its app ID; it still
keeps Xray's own connections out of the TUN's routes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 11:13:10 +03:30
41 changed files with 224 additions and 314 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ RUN cat <<EOF >/tmp/usr/local/etc/xray/00_log.json
{ {
"log": { "log": {
"error": "/var/log/xray/error.log", "error": "/var/log/xray/error.log",
"logLevel": "warning", "loglevel": "warning",
"access": "none", "access": "none",
"dnsLog": false "dnsLog": false
} }
+1 -1
View File
@@ -20,7 +20,7 @@ RUN cat <<EOF >/tmp/usr/local/etc/xray/00_log.json
{ {
"log": { "log": {
"error": "/var/log/xray/error.log", "error": "/var/log/xray/error.log",
"logLevel": "warning", "loglevel": "warning",
"access": "none", "access": "none",
"dnsLog": false "dnsLog": false
} }
+2 -2
View File
@@ -3,12 +3,12 @@ package burst
import ( import (
"context" "context"
"fmt" "fmt"
"math/rand/v2"
"strings" "strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
) )
@@ -181,7 +181,7 @@ func (h *HealthPing) doCheck(ctx context.Context, tags []string, duration time.D
for i := 0; i < rounds; i++ { for i := 0; i < rounds; i++ {
delay := time.Duration(0) delay := time.Duration(0)
if duration > 0 { if duration > 0 {
delay = rand.N(duration) delay = time.Duration(dice.RollInt63n(int64(duration)))
} }
timers = append(timers, time.AfterFunc(delay, func() { timers = append(timers, time.AfterFunc(delay, func() {
errors.LogDebug(h.ctx, "checking ", handler) errors.LogDebug(h.ctx, "checking ", handler)
+2 -2
View File
@@ -7,7 +7,7 @@ import (
"io" "io"
"math/big" "math/big"
mrand "math/rand/v2" "github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/app/proxyman" "github.com/xtls/xray-core/app/proxyman"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
@@ -196,7 +196,7 @@ func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) {
} }
} else { } else {
unchangedDomain := ob.Target.Address.Domain() unchangedDomain := ob.Target.Address.Domain()
ob.Target.Address = net.IPAddress(ips[mrand.N(len(ips))]) ob.Target.Address = net.IPAddress(ips[dice.Roll(len(ips))])
errors.LogInfo(ctx, "target: ", unchangedDomain, " resolved to: ", ob.Target.Address.String()) errors.LogInfo(ctx, "target: ", unchangedDomain, " resolved to: ", ob.Target.Address.String())
} }
} }
+3 -2
View File
@@ -3,11 +3,12 @@ package reverse
import ( import (
"crypto/rand" "crypto/rand"
"io" "io"
mrand "math/rand/v2"
"github.com/xtls/xray-core/common/dice"
) )
func (c *Control) FillInRandom() { func (c *Control) FillInRandom() {
randomLength := mrand.N(64) randomLength := dice.Roll(64)
randomLength++ randomLength++
c.Random = make([]byte, randomLength) c.Random = make([]byte, randomLength)
io.ReadFull(rand.Reader, c.Random) io.ReadFull(rand.Reader, c.Random)
+2 -2
View File
@@ -3,13 +3,13 @@ package router
import ( import (
"context" "context"
"math" "math"
"math/rand/v2"
"slices" "slices"
"sort" "sort"
"time" "time"
"github.com/xtls/xray-core/app/observatory" "github.com/xtls/xray-core/app/observatory"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/extension" "github.com/xtls/xray-core/features/extension"
@@ -74,7 +74,7 @@ func (s *LeastLoadStrategy) PickOutbound(candidates []string) string {
// goes to fallbackTag // goes to fallbackTag
return "" return ""
} }
return selects[rand.N(count)].Tag return selects[dice.Roll(count)].Tag
} }
func (s *LeastLoadStrategy) pickOutbounds(candidates []string) []*node { func (s *LeastLoadStrategy) pickOutbounds(candidates []string) []*node {
+2 -2
View File
@@ -2,10 +2,10 @@ package router
import ( import (
"context" "context"
"math/rand/v2"
"github.com/xtls/xray-core/app/observatory" "github.com/xtls/xray-core/app/observatory"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/extension" "github.com/xtls/xray-core/features/extension"
) )
@@ -63,5 +63,5 @@ func (s *RandomStrategy) PickOutbound(candidates []string) string {
// goes to fallbackTag // goes to fallbackTag
return "" return ""
} }
return candidates[rand.N(count)] return candidates[dice.Roll(count)]
} }
+1 -1
View File
@@ -73,7 +73,7 @@ func (r *TimeoutWrapperReader) ReadMultiBufferTimeout(duration time.Duration) (M
} }
return r.mb, r.err return r.mb, r.err
case <-timeout: case <-timeout:
return nil, ErrReadTimeout return nil, nil
} }
} }
+55
View File
@@ -0,0 +1,55 @@
// Package dice contains common functions to generate random number.
// It also initialize math/rand with the time in seconds at launch time.
package dice // import "github.com/xtls/xray-core/common/dice"
import (
"math/rand"
)
// Roll returns a non-negative number between 0 (inclusive) and n (exclusive).
func Roll(n int) int {
if n == 1 {
return 0
}
return rand.Intn(n)
}
// RollInt63n returns a non-negative number between 0 (inclusive) and n (exclusive).
func RollInt63n(n int64) int64 {
if n == 1 {
return 0
}
return rand.Int63n(n)
}
// Roll returns a non-negative number between 0 (inclusive) and n (exclusive).
func RollDeterministic(n int, seed int64) int {
if n == 1 {
return 0
}
return rand.New(rand.NewSource(seed)).Intn(n)
}
// RollUint16 returns a random uint16 value.
func RollUint16() uint16 {
return uint16(rand.Int63() >> 47)
}
func RollUint64() uint64 {
return rand.Uint64()
}
func NewDeterministicDice(seed int64) *DeterministicDice {
return &DeterministicDice{rand.New(rand.NewSource(seed))}
}
type DeterministicDice struct {
*rand.Rand
}
func (dd *DeterministicDice) Roll(n int) int {
if n == 1 {
return 0
}
return dd.Intn(n)
}
+50
View File
@@ -0,0 +1,50 @@
package dice_test
import (
"math/rand"
"testing"
. "github.com/xtls/xray-core/common/dice"
)
func BenchmarkRoll1(b *testing.B) {
for i := 0; i < b.N; i++ {
Roll(1)
}
}
func BenchmarkRoll20(b *testing.B) {
for i := 0; i < b.N; i++ {
Roll(20)
}
}
func BenchmarkIntn1(b *testing.B) {
for i := 0; i < b.N; i++ {
rand.Intn(1)
}
}
func BenchmarkIntn20(b *testing.B) {
for i := 0; i < b.N; i++ {
rand.Intn(20)
}
}
func BenchmarkInt63(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = uint16(rand.Int63() >> 47)
}
}
func BenchmarkInt31(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = uint16(rand.Int31() >> 15)
}
}
func BenchmarkIntn(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = uint16(rand.Intn(65536))
}
}
+5 -5
View File
@@ -2,8 +2,8 @@ package drain
import ( import (
"io" "io"
"math/rand/v2"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
) )
@@ -12,10 +12,10 @@ type BehaviorSeedLimitedDrainer struct {
} }
func NewBehaviorSeedLimitedDrainer(behaviorSeed int64, drainFoundation, maxBaseDrainSize, maxRandDrain int) (Drainer, error) { func NewBehaviorSeedLimitedDrainer(behaviorSeed int64, drainFoundation, maxBaseDrainSize, maxRandDrain int) (Drainer, error) {
behaviorRand := rand.New(rand.NewPCG(uint64(behaviorSeed), 0)) behaviorRand := dice.NewDeterministicDice(behaviorSeed)
BaseDrainSize := behaviorRand.N(maxBaseDrainSize) BaseDrainSize := behaviorRand.Roll(maxBaseDrainSize)
RandDrainMax := behaviorRand.N(maxRandDrain) + 1 RandDrainMax := behaviorRand.Roll(maxRandDrain) + 1
RandDrainRolled := rand.N(RandDrainMax) RandDrainRolled := dice.Roll(RandDrainMax)
DrainSize := drainFoundation + BaseDrainSize + RandDrainRolled DrainSize := drainFoundation + BaseDrainSize + RandDrainRolled
return &BehaviorSeedLimitedDrainer{DrainSize: DrainSize}, nil return &BehaviorSeedLimitedDrainer{DrainSize: DrainSize}, nil
} }
+3 -2
View File
@@ -1,8 +1,9 @@
package protocol package protocol
import ( import (
"math/rand/v2"
"time" "time"
"github.com/xtls/xray-core/common/dice"
) )
type Timestamp int64 type Timestamp int64
@@ -15,7 +16,7 @@ func NowTime() Timestamp {
func NewTimestampGenerator(base Timestamp, delta int) TimestampGenerator { func NewTimestampGenerator(base Timestamp, delta int) TimestampGenerator {
return func() Timestamp { return func() Timestamp {
rangeInDelta := rand.N(delta*2) - delta rangeInDelta := dice.Roll(delta*2) - delta
return base + Timestamp(rangeInDelta) return base + Timestamp(rangeInDelta)
} }
} }
+1 -1
View File
@@ -134,7 +134,7 @@ func TestMarshalConfigJson(t *testing.T) {
func getConfig() string { func getConfig() string {
return `{ return `{
"log": { "log": {
"logLevel": "debug" "loglevel": "debug"
}, },
"stats": {}, "stats": {},
"policy": { "policy": {
+1 -1
View File
@@ -10,7 +10,7 @@ require (
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.2 github.com/klauspost/cpuid/v2 v2.4.1
github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138 github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138
github.com/miekg/dns v1.1.73 github.com/miekg/dns v1.1.73
github.com/pelletier/go-toml v1.9.5 github.com/pelletier/go-toml v1.9.5
+2 -2
View File
@@ -35,8 +35,8 @@ 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.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA= github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
github.com/klauspost/cpuid/v2 v2.4.2 h1:xRw7IZEsAgVCp6trS/DgWbTAqpixhmywgVgBjYGg1bQ= github.com/klauspost/cpuid/v2 v2.4.1 h1:I9KxadAkvo+QjtcEE2f2tyfEvmIla/T0m+14PrZNl0U=
github.com/klauspost/cpuid/v2 v2.4.2/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU= github.com/klauspost/cpuid/v2 v2.4.1/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= 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/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=
+3 -33
View File
@@ -9,7 +9,6 @@ import (
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/geodata" "github.com/xtls/xray-core/common/geodata"
"github.com/xtls/xray-core/common/log"
xnet "github.com/xtls/xray-core/common/net" xnet "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/proxy/freedom" "github.com/xtls/xray-core/proxy/freedom"
@@ -49,8 +48,6 @@ type FreedomFinalRuleConfig struct {
Network *NetworkList `json:"network"` Network *NetworkList `json:"network"`
Port *PortList `json:"port"` Port *PortList `json:"port"`
IP *StringList `json:"ip"` IP *StringList `json:"ip"`
Delay *Int32Range `json:"delay"`
LogLevel string `json:"logLevel"`
BlockDelay *Int32Range `json:"blockDelay"` BlockDelay *Int32Range `json:"blockDelay"`
} }
@@ -283,37 +280,10 @@ func (c *FreedomFinalRuleConfig) Build() (*freedom.FinalRuleConfig, error) {
} }
if c.BlockDelay != nil { if c.BlockDelay != nil {
errors.PrintDeprecatedFeatureWarning("finalRules.blockDelay", "finalRules.delay") rule.BlockDelay = &freedom.Range{
c.Delay = c.BlockDelay Min: uint64(c.BlockDelay.From),
} Max: uint64(c.BlockDelay.To),
if rule.Action != freedom.RuleAction_Block {
if c.Delay != nil || c.LogLevel != "" {
return nil, errors.New("delay and logLevel are only supported for block finalRules")
} }
return rule, nil
}
if c.Delay != nil {
rule.Delay = &freedom.Range{
Min: uint64(c.Delay.From),
Max: uint64(c.Delay.To),
}
}
switch strings.ToLower(c.LogLevel) {
case "", "info":
rule.LogLevel = log.Severity_Info.Enum()
case "none":
rule.LogLevel = log.Severity_Unknown.Enum()
case "debug":
rule.LogLevel = log.Severity_Debug.Enum()
case "warning":
rule.LogLevel = log.Severity_Warning.Enum()
case "error":
rule.LogLevel = log.Severity_Error.Enum()
default:
return nil, errors.New("unknown finalRules.logLevel: ", c.LogLevel)
} }
return rule, nil return rule, nil
+5 -5
View File
@@ -4,7 +4,6 @@ import (
"testing" "testing"
"github.com/xtls/xray-core/common/geodata" "github.com/xtls/xray-core/common/geodata"
"github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
. "github.com/xtls/xray-core/infra/conf" . "github.com/xtls/xray-core/infra/conf"
@@ -47,8 +46,7 @@ func TestFreedomConfig(t *testing.T) {
"network": "tcp,udp", "network": "tcp,udp",
"port": "53,443", "port": "53,443",
"ip": ["10.0.0.0/8", "2001:db8::/32"], "ip": ["10.0.0.0/8", "2001:db8::/32"],
"delay": "30-60", "blockDelay": "30-60"
"logLevel": "debug"
}, { }, {
"action": "allow", "action": "allow",
"network": ["udp"] "network": ["udp"]
@@ -88,8 +86,10 @@ func TestFreedomConfig(t *testing.T) {
}, },
}, },
}, },
Delay: &freedom.Range{Min: 30, Max: 60}, BlockDelay: &freedom.Range{
LogLevel: log.Severity_Debug.Enum(), Min: 30,
Max: 60,
},
}, },
{ {
Action: freedom.RuleAction_Allow, Action: freedom.RuleAction_Allow,
+1 -1
View File
@@ -18,7 +18,7 @@ func DefaultLogConfig() *log.Config {
type LogConfig struct { type LogConfig struct {
AccessLog string `json:"access"` AccessLog string `json:"access"`
ErrorLog string `json:"error"` ErrorLog string `json:"error"`
LogLevel string `json:"logLevel"` LogLevel string `json:"loglevel"`
DNSLog bool `json:"dnsLog"` DNSLog bool `json:"dnsLog"`
MaskAddress string `json:"maskAddress"` MaskAddress string `json:"maskAddress"`
} }
+2 -2
View File
@@ -18,7 +18,7 @@ func TestLoaderError(t *testing.T) {
"log": { "log": {
// abcd // abcd
0, 0,
"logLevel": "info" "loglevel": "info"
} }
}`, }`,
Output: "line 4 char 6", Output: "line 4 char 6",
@@ -27,7 +27,7 @@ func TestLoaderError(t *testing.T) {
Input: `{ Input: `{
"log": { "log": {
// abcd // abcd
"logLevel": "info", "loglevel": "info",
} }
}`, }`,
Output: "line 5 char 5", Output: "line 5 char 5",
+1 -1
View File
@@ -42,7 +42,7 @@ func TestXrayConfig(t *testing.T) {
Input: `{ Input: `{
"log": { "log": {
"access": "/var/log/xray/access.log", "access": "/var/log/xray/access.log",
"logLevel": "error", "loglevel": "error",
"error": "/var/log/xray/error.log" "error": "/var/log/xray/error.log"
}, },
"inbounds": [{ "inbounds": [{
+2 -2
View File
@@ -4,12 +4,12 @@ package blackhole
import ( import (
"bytes" "bytes"
"context" "context"
"math/rand/v2"
"net/http" "net/http"
"time" "time"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
@@ -74,7 +74,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, func() { timer := signal.CancelAfterInactivity(ctx, func() {
cancel() cancel()
}, time.Duration(30+rand.N(61))*time.Second) }, time.Duration(30+dice.Roll(61))*time.Second)
go buf.Copy(link.Reader, buf.Discard, buf.UpdateActivity(timer)) go buf.Copy(link.Reader, buf.Discard, buf.UpdateActivity(timer))
<-ctx.Done() <-ctx.Done()
} }
+20 -34
View File
@@ -8,7 +8,6 @@ package freedom
import ( import (
geodata "github.com/xtls/xray-core/common/geodata" geodata "github.com/xtls/xray-core/common/geodata"
log "github.com/xtls/xray-core/common/log"
net "github.com/xtls/xray-core/common/net" net "github.com/xtls/xray-core/common/net"
protocol "github.com/xtls/xray-core/common/protocol" protocol "github.com/xtls/xray-core/common/protocol"
internet "github.com/xtls/xray-core/transport/internet" internet "github.com/xtls/xray-core/transport/internet"
@@ -358,8 +357,7 @@ type FinalRuleConfig struct {
Networks []net.Network `protobuf:"varint,2,rep,packed,name=networks,proto3,enum=xray.common.net.Network" json:"networks,omitempty"` Networks []net.Network `protobuf:"varint,2,rep,packed,name=networks,proto3,enum=xray.common.net.Network" json:"networks,omitempty"`
PortList *net.PortList `protobuf:"bytes,3,opt,name=port_list,json=portList,proto3" json:"port_list,omitempty"` PortList *net.PortList `protobuf:"bytes,3,opt,name=port_list,json=portList,proto3" json:"port_list,omitempty"`
Ip []*geodata.IPRule `protobuf:"bytes,4,rep,name=ip,proto3" json:"ip,omitempty"` Ip []*geodata.IPRule `protobuf:"bytes,4,rep,name=ip,proto3" json:"ip,omitempty"`
Delay *Range `protobuf:"bytes,5,opt,name=delay,proto3" json:"delay,omitempty"` BlockDelay *Range `protobuf:"bytes,5,opt,name=block_delay,json=blockDelay,proto3" json:"block_delay,omitempty"`
LogLevel *log.Severity `protobuf:"varint,6,opt,name=log_level,json=logLevel,proto3,enum=xray.common.log.Severity,oneof" json:"log_level,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -422,20 +420,13 @@ func (x *FinalRuleConfig) GetIp() []*geodata.IPRule {
return nil return nil
} }
func (x *FinalRuleConfig) GetDelay() *Range { func (x *FinalRuleConfig) GetBlockDelay() *Range {
if x != nil { if x != nil {
return x.Delay return x.BlockDelay
} }
return nil return nil
} }
func (x *FinalRuleConfig) GetLogLevel() log.Severity {
if x != nil && x.LogLevel != nil {
return *x.LogLevel
}
return log.Severity(0)
}
type Config struct { type Config struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
DomainStrategy internet.DomainStrategy `protobuf:"varint,1,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"domain_strategy,omitempty"` DomainStrategy internet.DomainStrategy `protobuf:"varint,1,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"domain_strategy,omitempty"`
@@ -532,7 +523,7 @@ var File_proxy_freedom_config_proto protoreflect.FileDescriptor
const file_proxy_freedom_config_proto_rawDesc = "" + const file_proxy_freedom_config_proto_rawDesc = "" +
"\n" + "\n" +
"\x1aproxy/freedom/config.proto\x12\x12xray.proxy.freedom\x1a!common/protocol/server_spec.proto\x1a\x1ftransport/internet/config.proto\x1a\x15common/net/port.proto\x1a\x18common/net/network.proto\x1a\x1bcommon/geodata/geodat.proto\x1a\x14common/log/log.proto\"S\n" + "\x1aproxy/freedom/config.proto\x12\x12xray.proxy.freedom\x1a!common/protocol/server_spec.proto\x1a\x1ftransport/internet/config.proto\x1a\x15common/net/port.proto\x1a\x18common/net/network.proto\x1a\x1bcommon/geodata/geodat.proto\"S\n" +
"\x13DestinationOverride\x12<\n" + "\x13DestinationOverride\x12<\n" +
"\x06server\x18\x01 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06server\"\x98\x02\n" + "\x06server\x18\x01 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06server\"\x98\x02\n" +
"\bFragment\x12!\n" + "\bFragment\x12!\n" +
@@ -558,16 +549,14 @@ const file_proxy_freedom_config_proto_rawDesc = "" +
"\bapply_to\x18\x06 \x01(\tR\aapplyTo\"+\n" + "\bapply_to\x18\x06 \x01(\tR\aapplyTo\"+\n" +
"\x05Range\x12\x10\n" + "\x05Range\x12\x10\n" +
"\x03min\x18\x01 \x01(\x04R\x03min\x12\x10\n" + "\x03min\x18\x01 \x01(\x04R\x03min\x12\x10\n" +
"\x03max\x18\x02 \x01(\x04R\x03max\"\xe0\x02\n" + "\x03max\x18\x02 \x01(\x04R\x03max\"\xa0\x02\n" +
"\x0fFinalRuleConfig\x126\n" + "\x0fFinalRuleConfig\x126\n" +
"\x06action\x18\x01 \x01(\x0e2\x1e.xray.proxy.freedom.RuleActionR\x06action\x124\n" + "\x06action\x18\x01 \x01(\x0e2\x1e.xray.proxy.freedom.RuleActionR\x06action\x124\n" +
"\bnetworks\x18\x02 \x03(\x0e2\x18.xray.common.net.NetworkR\bnetworks\x126\n" + "\bnetworks\x18\x02 \x03(\x0e2\x18.xray.common.net.NetworkR\bnetworks\x126\n" +
"\tport_list\x18\x03 \x01(\v2\x19.xray.common.net.PortListR\bportList\x12+\n" + "\tport_list\x18\x03 \x01(\v2\x19.xray.common.net.PortListR\bportList\x12+\n" +
"\x02ip\x18\x04 \x03(\v2\x1b.xray.common.geodata.IPRuleR\x02ip\x12/\n" + "\x02ip\x18\x04 \x03(\v2\x1b.xray.common.geodata.IPRuleR\x02ip\x12:\n" +
"\x05delay\x18\x05 \x01(\v2\x19.xray.proxy.freedom.RangeR\x05delay\x12;\n" + "\vblock_delay\x18\x05 \x01(\v2\x19.xray.proxy.freedom.RangeR\n" +
"\tlog_level\x18\x06 \x01(\x0e2\x19.xray.common.log.SeverityH\x00R\blogLevel\x88\x01\x01B\f\n" + "blockDelay\"\xaf\x03\n" +
"\n" +
"_log_level\"\xaf\x03\n" +
"\x06Config\x12P\n" + "\x06Config\x12P\n" +
"\x0fdomain_strategy\x18\x01 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0edomainStrategy\x12Z\n" + "\x0fdomain_strategy\x18\x01 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0edomainStrategy\x12Z\n" +
"\x14destination_override\x18\x03 \x01(\v2'.xray.proxy.freedom.DestinationOverrideR\x13destinationOverride\x12\x1d\n" + "\x14destination_override\x18\x03 \x01(\v2'.xray.proxy.freedom.DestinationOverrideR\x13destinationOverride\x12\x1d\n" +
@@ -610,8 +599,7 @@ var file_proxy_freedom_config_proto_goTypes = []any{
(net.Network)(0), // 8: xray.common.net.Network (net.Network)(0), // 8: xray.common.net.Network
(*net.PortList)(nil), // 9: xray.common.net.PortList (*net.PortList)(nil), // 9: xray.common.net.PortList
(*geodata.IPRule)(nil), // 10: xray.common.geodata.IPRule (*geodata.IPRule)(nil), // 10: xray.common.geodata.IPRule
(log.Severity)(0), // 11: xray.common.log.Severity (internet.DomainStrategy)(0), // 11: xray.transport.internet.DomainStrategy
(internet.DomainStrategy)(0), // 12: xray.transport.internet.DomainStrategy
} }
var file_proxy_freedom_config_proto_depIdxs = []int32{ var file_proxy_freedom_config_proto_depIdxs = []int32{
7, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint 7, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint
@@ -619,18 +607,17 @@ var file_proxy_freedom_config_proto_depIdxs = []int32{
8, // 2: xray.proxy.freedom.FinalRuleConfig.networks:type_name -> xray.common.net.Network 8, // 2: xray.proxy.freedom.FinalRuleConfig.networks:type_name -> xray.common.net.Network
9, // 3: xray.proxy.freedom.FinalRuleConfig.port_list:type_name -> xray.common.net.PortList 9, // 3: xray.proxy.freedom.FinalRuleConfig.port_list:type_name -> xray.common.net.PortList
10, // 4: xray.proxy.freedom.FinalRuleConfig.ip:type_name -> xray.common.geodata.IPRule 10, // 4: xray.proxy.freedom.FinalRuleConfig.ip:type_name -> xray.common.geodata.IPRule
4, // 5: xray.proxy.freedom.FinalRuleConfig.delay:type_name -> xray.proxy.freedom.Range 4, // 5: xray.proxy.freedom.FinalRuleConfig.block_delay:type_name -> xray.proxy.freedom.Range
11, // 6: xray.proxy.freedom.FinalRuleConfig.log_level:type_name -> xray.common.log.Severity 11, // 6: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
12, // 7: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.transport.internet.DomainStrategy 1, // 7: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride
1, // 8: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride 2, // 8: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment
2, // 9: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment 3, // 9: xray.proxy.freedom.Config.noises:type_name -> xray.proxy.freedom.Noise
3, // 10: xray.proxy.freedom.Config.noises:type_name -> xray.proxy.freedom.Noise 5, // 10: xray.proxy.freedom.Config.final_rules:type_name -> xray.proxy.freedom.FinalRuleConfig
5, // 11: xray.proxy.freedom.Config.final_rules:type_name -> xray.proxy.freedom.FinalRuleConfig 11, // [11:11] is the sub-list for method output_type
12, // [12:12] is the sub-list for method output_type 11, // [11:11] is the sub-list for method input_type
12, // [12:12] is the sub-list for method input_type 11, // [11:11] is the sub-list for extension type_name
12, // [12:12] is the sub-list for extension type_name 11, // [11:11] is the sub-list for extension extendee
12, // [12:12] is the sub-list for extension extendee 0, // [0:11] is the sub-list for field type_name
0, // [0:12] is the sub-list for field type_name
} }
func init() { file_proxy_freedom_config_proto_init() } func init() { file_proxy_freedom_config_proto_init() }
@@ -638,7 +625,6 @@ func file_proxy_freedom_config_proto_init() {
if File_proxy_freedom_config_proto != nil { if File_proxy_freedom_config_proto != nil {
return return
} }
file_proxy_freedom_config_proto_msgTypes[4].OneofWrappers = []any{}
type x struct{} type x struct{}
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
+1 -3
View File
@@ -11,7 +11,6 @@ import "transport/internet/config.proto";
import "common/net/port.proto"; import "common/net/port.proto";
import "common/net/network.proto"; import "common/net/network.proto";
import "common/geodata/geodat.proto"; import "common/geodata/geodat.proto";
import "common/log/log.proto";
message DestinationOverride { message DestinationOverride {
xray.common.protocol.ServerEndpoint server = 1; xray.common.protocol.ServerEndpoint server = 1;
@@ -52,8 +51,7 @@ message FinalRuleConfig {
repeated xray.common.net.Network networks = 2; repeated xray.common.net.Network networks = 2;
xray.common.net.PortList port_list = 3; xray.common.net.PortList port_list = 3;
repeated xray.common.geodata.IPRule ip = 4; repeated xray.common.geodata.IPRule ip = 4;
Range delay = 5; Range block_delay = 5;
optional xray.common.log.Severity log_level = 6;
} }
message Config { message Config {
+23 -45
View File
@@ -12,9 +12,9 @@ import (
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/crypto" "github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/geodata" "github.com/xtls/xray-core/common/geodata"
"github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/platform"
"github.com/xtls/xray-core/common/retry" "github.com/xtls/xray-core/common/retry"
@@ -29,7 +29,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"
mrand "math/rand/v2"
) )
var ( var (
@@ -73,26 +72,23 @@ func init() {
} }
defaultBlockPrivateRule = &FinalRule{ defaultBlockPrivateRule = &FinalRule{
action: RuleAction_Block, action: RuleAction_Block,
network: allNetworks, network: allNetworks,
ip: geodata.GetPrivateIPMatcher(), ip: geodata.GetPrivateIPMatcher(),
logLevel: log.Severity_Warning.Enum(),
} }
defaultBlockAllRule = &FinalRule{ defaultBlockAllRule = &FinalRule{
action: RuleAction_Block, action: RuleAction_Block,
network: allNetworks, network: allNetworks,
logLevel: log.Severity_Warning.Enum(),
} }
} }
type FinalRule struct { type FinalRule struct {
action RuleAction action RuleAction
network [8]bool network [8]bool
port net.MemoryPortList port net.MemoryPortList
ip geodata.IPMatcher ip geodata.IPMatcher
delay *Range blockDelay *Range
logLevel *log.Severity
} }
// Handler handles Freedom connections. // Handler handles Freedom connections.
@@ -106,9 +102,8 @@ type Handler struct {
func buildFinalRule(config *FinalRuleConfig) (*FinalRule, error) { func buildFinalRule(config *FinalRuleConfig) (*FinalRule, error) {
rule := &FinalRule{ rule := &FinalRule{
action: config.GetAction(), action: config.GetAction(),
delay: config.GetDelay(), blockDelay: config.GetBlockDelay(),
logLevel: config.LogLevel,
} }
if len(config.Networks) == 0 { if len(config.Networks) == 0 {
@@ -169,7 +164,7 @@ func getDefaultFinalRule(inbound *session.Inbound) *FinalRule {
switch inbound.Name { switch inbound.Name {
case "vless-reverse": case "vless-reverse":
return defaultBlockAllRule return defaultBlockAllRule
case "vless", "vmess", "trojan", "hysteria", "wireguard", "masque": case "vless", "vmess", "trojan", "hysteria", "wireguard":
return defaultBlockPrivateRule return defaultBlockPrivateRule
default: default:
if strings.HasPrefix(inbound.Name, "shadowsocks") { if strings.HasPrefix(inbound.Name, "shadowsocks") {
@@ -217,40 +212,23 @@ func (h *Handler) policy() policy.Session {
return p return p
} }
func (r *FinalRule) blockDuration() time.Duration { func (h *Handler) blockDelay(rule *FinalRule) time.Duration {
min := uint64(30) min := uint64(30)
max := uint64(90) max := uint64(90)
if r.delay != nil { if rule.blockDelay != nil {
min = r.delay.Min min = rule.blockDelay.Min
max = r.delay.Max max = rule.blockDelay.Max
} }
span := max - min span := max - min
if max < min { if max < min {
span = min - max span = min - max
} }
return time.Duration(min+mrand.N(span+1)) * time.Second return time.Duration(min+uint64(dice.Roll(int(span+1)))) * time.Second
}
func (r *FinalRule) log(ctx context.Context, msg ...interface{}) {
level := log.Severity_Info
if r.logLevel != nil {
level = *r.logLevel
}
switch level {
case log.Severity_Debug:
errors.LogDebug(ctx, msg...)
case log.Severity_Info:
errors.LogInfo(ctx, msg...)
case log.Severity_Warning:
errors.LogWarning(ctx, msg...)
case log.Severity_Error:
errors.LogError(ctx, msg...)
}
} }
func (h *Handler) blackhole(ctx context.Context, input buf.Reader, output buf.Writer, rule *FinalRule, dest *net.Destination) error { func (h *Handler) blackhole(ctx context.Context, input buf.Reader, output buf.Writer, rule *FinalRule, dest *net.Destination) error {
delay := rule.blockDuration() delay := h.blockDelay(rule)
rule.log(ctx, "target blocked by a finalrule: ", *dest, ", blackholing connection for ", delay) errors.LogInfo(ctx, "blocked target: ", *dest, ", blackholing connection for ", delay)
defer common.Close(output) defer common.Close(output)
done := make(chan struct{}) done := make(chan struct{})
go func() { // a DispatchLink inbound's reader ignores Interrupt, so don't wait for this copy go func() { // a DispatchLink inbound's reader ignores Interrupt, so don't wait for this copy
@@ -262,7 +240,7 @@ func (h *Handler) blackhole(ctx context.Context, input buf.Reader, output buf.Wr
case <-time.After(delay): case <-time.After(delay):
common.Interrupt(input) common.Interrupt(input)
common.Interrupt(output) common.Interrupt(output)
rule.log(ctx, "closed blackholed connection to blocked target: ", *dest) errors.LogInfo(ctx, "closed blackholed connection to blocked target: ", *dest)
} }
return nil return nil
} }
@@ -634,7 +612,7 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
continue continue
} }
} else { } else {
ip = net.IPAddress(ips[mrand.N(len(ips))]) ip = net.IPAddress(ips[dice.Roll(len(ips))])
shouldUseSystemResolver = false shouldUseSystemResolver = false
} }
} }
+2 -2
View File
@@ -5,10 +5,10 @@ import (
"crypto/hmac" "crypto/hmac"
"crypto/sha256" "crypto/sha256"
"hash/crc64" "hash/crc64"
"math/rand/v2"
"strings" "strings"
"sync" "sync"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
) )
@@ -158,7 +158,7 @@ func (v *Validator) GetBehaviorSeed() uint64 {
v.behaviorFused = true v.behaviorFused = true
if v.behaviorSeed == 0 { if v.behaviorSeed == 0 {
v.behaviorSeed = rand.Uint64() v.behaviorSeed = dice.RollUint64()
} }
return v.behaviorSeed return v.behaviorSeed
} }
-6
View File
@@ -201,12 +201,6 @@ startOver:
} }
err = t.luid.SetDNS(family, dns, nil) err = t.luid.SetDNS(family, dns, nil)
if err != nil { if err != nil {
// Skip IPv6 the config does not ask for also when setting its DNS
// servers fails, as reported on Windows 11 24H2 with IPv6 disabled
// (Access is denied).
if family == windows.AF_INET6 && !address6 && !route6 {
continue
}
firstErr = errors.New("unable to set DNS").Base(err) firstErr = errors.New("unable to set DNS").Base(err)
if err == windows.ERROR_NOT_FOUND { if err == windows.ERROR_NOT_FOUND {
goto startOver goto startOver
+2 -2
View File
@@ -13,13 +13,13 @@ import (
"github.com/xtls/xray-core/common/bitmask" "github.com/xtls/xray-core/common/bitmask"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/crypto" "github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/drain" "github.com/xtls/xray-core/common/drain"
"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/proxy/vmess" "github.com/xtls/xray-core/proxy/vmess"
vmessaead "github.com/xtls/xray-core/proxy/vmess/aead" vmessaead "github.com/xtls/xray-core/proxy/vmess/aead"
"golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/chacha20poly1305"
mrand "math/rand/v2"
) )
// ClientSession stores connection session info for VMess client. // ClientSession stores connection session info for VMess client.
@@ -72,7 +72,7 @@ func (c *ClientSession) EncodeRequestHeader(header *protocol.RequestHeader, writ
common.Must(buffer.WriteByte(c.responseHeader)) common.Must(buffer.WriteByte(c.responseHeader))
common.Must(buffer.WriteByte(byte(header.Option))) common.Must(buffer.WriteByte(byte(header.Option)))
paddingLen := mrand.N(16) paddingLen := dice.Roll(16)
security := byte(paddingLen<<4) | byte(header.Security) security := byte(paddingLen<<4) | byte(header.Security)
common.Must2(buffer.Write([]byte{security, byte(0), byte(header.Command)})) common.Must2(buffer.Write([]byte{security, byte(0), byte(header.Command)}))
+2 -2
View File
@@ -4,10 +4,10 @@ import (
"crypto/hmac" "crypto/hmac"
"crypto/sha256" "crypto/sha256"
"hash/crc64" "hash/crc64"
"math/rand/v2"
"strings" "strings"
"sync" "sync"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/proxy/vmess/aead" "github.com/xtls/xray-core/proxy/vmess/aead"
@@ -117,7 +117,7 @@ func (v *TimedUserValidator) GetBehaviorSeed() uint64 {
v.behaviorFused = true v.behaviorFused = true
if v.behaviorSeed == 0 { if v.behaviorSeed == 0 {
v.behaviorSeed = rand.Uint64() v.behaviorSeed = dice.RollUint64()
} }
return v.behaviorSeed return v.behaviorSeed
} }
+2 -6
View File
@@ -17,7 +17,7 @@ import (
type bind struct { type bind struct {
ctx context.Context ctx context.Context
resolveFunc func(ctx context.Context, host string) (net.IP, error) resolveFunc func(host string) (net.IP, error)
listenFunc func(ctx context.Context) (net.PacketConn, error) listenFunc func(ctx context.Context) (net.PacketConn, error)
downFunc func() error downFunc func() error
reserved []byte reserved []byte
@@ -143,11 +143,7 @@ func (b *bind) ParseEndpoint(s string) (conn.Endpoint, error) {
if port < 0 || port > 65535 { if port < 0 || port > 65535 {
return nil, errors.New("invalid port " + sport) return nil, errors.New("invalid port " + sport)
} }
// up sets the connection's context right before it hands over the endpoint ip, err := b.resolveFunc(host)
b.mu.Lock()
ctx := b.ctx
b.mu.Unlock()
ip, err := b.resolveFunc(ctx, host)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+5 -26
View File
@@ -3,7 +3,6 @@ package wireguard
import ( import (
"context" "context"
"fmt" "fmt"
"math/rand/v2"
"net/netip" "net/netip"
"reflect" "reflect"
"strings" "strings"
@@ -15,6 +14,7 @@ import (
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
@@ -323,12 +323,12 @@ func (h *Handler) up(ctx context.Context) error {
return h.dev.Up() return h.dev.Up()
} }
func MakeResolveLocal(d dns.Client, streamSettings *internet.MemoryStreamConfig) func(ctx context.Context, host string) (net.IP, error) { func MakeResolveLocal(d dns.Client, streamSettings *internet.MemoryStreamConfig) func(host string) (net.IP, error) {
return func(ctx context.Context, host string) (net.IP, error) { return func(host string) (net.IP, error) {
if ip := net.ParseIP(host); ip != nil { if ip := net.ParseIP(host); ip != nil {
return ip, nil return ip, nil
} }
ips, err := lookupIPContext(ctx, d, host) ips, _, err := d.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -364,28 +364,7 @@ func MakeResolveLocal(d dns.Client, streamSettings *internet.MemoryStreamConfig)
return nil, dns.ErrEmptyResponse return nil, dns.ErrEmptyResponse
} }
} }
return got[rand.N(len(got))], nil return got[dice.Roll(len(got))], nil
}
}
func lookupIPContext(ctx context.Context, d dns.Client, host string) ([]net.IP, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
type result struct {
ips []net.IP
err error
}
done := make(chan result, 1)
go func() {
ips, _, err := d.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
done <- result{ips, err}
}()
select {
case r := <-done:
return r.ips, r.err
case <-ctx.Done():
return nil, ctx.Err()
} }
} }
+8 -8
View File
@@ -661,7 +661,7 @@ func TestVlessRealityFingerprints(t *testing.T) {
wg.Wait() wg.Wait()
} }
// A blocked connection must close after finalRules.delay even while the client keeps it open and sends nothing. // A blocked connection must be closed after blockDelay even while the client keeps it open and sends nothing.
func TestVlessBlockDelay(t *testing.T) { func TestVlessBlockDelay(t *testing.T) {
tcpServer := tcp.Server{ tcpServer := tcp.Server{
MsgProcessor: xor, MsgProcessor: xor,
@@ -671,7 +671,7 @@ func TestVlessBlockDelay(t *testing.T) {
defer tcpServer.Close() defer tcpServer.Close()
for _, delay := range []uint64{0, 1} { for _, delay := range []uint64{0, 1} {
t.Run(fmt.Sprint("delay=", delay), func(t *testing.T) { t.Run(fmt.Sprint("blockDelay=", delay), func(t *testing.T) {
userID := protocol.NewID(uuid.New()) userID := protocol.NewID(uuid.New())
serverPort := tcp.PickPort() serverPort := tcp.PickPort()
serverConfig := &core.Config{ serverConfig := &core.Config{
@@ -697,8 +697,8 @@ func TestVlessBlockDelay(t *testing.T) {
ProxySettings: serial.ToTypedMessage(&freedom.Config{ ProxySettings: serial.ToTypedMessage(&freedom.Config{
FinalRules: []*freedom.FinalRuleConfig{ FinalRules: []*freedom.FinalRuleConfig{
{ {
Action: freedom.RuleAction_Block, Action: freedom.RuleAction_Block,
Delay: &freedom.Range{Min: delay, Max: delay}, BlockDelay: &freedom.Range{Min: delay, Max: delay},
}, },
}, },
}), }),
@@ -726,15 +726,15 @@ func TestVlessBlockDelay(t *testing.T) {
Port: dest.Port, Port: dest.Port,
}, &encoding.Addons{})) }, &encoding.Addons{}))
blockDuration := time.Duration(delay) * time.Second blockDelay := time.Duration(delay) * time.Second
common.Must(conn.SetReadDeadline(time.Now().Add(blockDuration + 5*time.Second))) common.Must(conn.SetReadDeadline(time.Now().Add(blockDelay + 5*time.Second)))
_, err = io.Copy(io.Discard, conn) _, err = io.Copy(io.Discard, conn)
elapsed := time.Since(start) elapsed := time.Since(start)
if err, ok := err.(net.Error); ok && err.Timeout() { if err, ok := err.(net.Error); ok && err.Timeout() {
t.Fatalf("connection is still open after %v", elapsed) t.Fatalf("connection is still open after %v", elapsed)
} }
if elapsed < blockDuration { if elapsed < blockDelay {
t.Fatalf("connection is closed after %v, before finalRules.delay", elapsed) t.Fatalf("connection is closed after %v, before blockDelay", elapsed)
} }
}) })
} }
+2 -2
View File
@@ -3,10 +3,10 @@ package internet
import ( import (
"context" "context"
"fmt" "fmt"
"math/rand/v2"
"strings" "strings"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/net/cnc" "github.com/xtls/xray-core/common/net/cnc"
@@ -260,7 +260,7 @@ func DialSystem(ctx context.Context, dest net.Destination, sockopt *SocketConfig
return nil, err return nil, err
} }
} else if sockopt.HappyEyeballs == nil || sockopt.HappyEyeballs.TryDelayMs == 0 || sockopt.HappyEyeballs.MaxConcurrentTry == 0 || len(ips) < 2 || len(sockopt.DialerProxy) > 0 || dest.Network != net.Network_TCP { } else if sockopt.HappyEyeballs == nil || sockopt.HappyEyeballs.TryDelayMs == 0 || sockopt.HappyEyeballs.MaxConcurrentTry == 0 || len(ips) < 2 || len(sockopt.DialerProxy) > 0 || dest.Network != net.Network_TCP {
dest.Address = net.IPAddress(ips[rand.N(len(ips))]) dest.Address = net.IPAddress(ips[dice.Roll(len(ips))])
errors.LogInfo(ctx, "replace destination with "+dest.String()) errors.LogInfo(ctx, "replace destination with "+dest.String())
} else { } else {
return TcpRaceDial(ctx, src, ips, dest.Port, sockopt, dest.Address.String()) return TcpRaceDial(ctx, src, ips, dest.Port, sockopt, dest.Address.String())
@@ -3,7 +3,8 @@ package header
import ( import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"math/rand/v2"
"github.com/xtls/xray-core/common/dice"
) )
type dns struct { type dns struct {
@@ -16,7 +17,7 @@ func (h *dns) Size() int {
func (h *dns) Serialize(b []byte) { func (h *dns) Serialize(b []byte) {
copy(b, h.header) copy(b, h.header)
binary.BigEndian.PutUint16(b[0:], uint16(rand.Uint32())) binary.BigEndian.PutUint16(b[0:], dice.RollUint16())
} }
func NewHeaderDNS(domain string) (*dns, error) { func NewHeaderDNS(domain string) (*dns, error) {
+1 -2
View File
@@ -6,7 +6,6 @@ import (
goerrors "errors" goerrors "errors"
"io" "io"
mrand "math/rand" mrand "math/rand"
stdnet "net"
"net/netip" "net/netip"
"sync" "sync"
"time" "time"
@@ -165,7 +164,7 @@ func (c *udpHopConn) recv(conn net.PacketConn) {
n, addr, err := conn.ReadFrom(p) n, addr, err := conn.ReadFrom(p)
if err != nil { if err != nil {
pool.Put(p[:cap(p)]) pool.Put(p[:cap(p)])
if c.closed() || goerrors.Is(err, stdnet.ErrClosed) { if c.closed() {
return return
} }
var netErr net.Error var netErr net.Error
+3 -2
View File
@@ -1,8 +1,9 @@
package http package http
import ( import (
"math/rand/v2"
"strings" "strings"
"github.com/xtls/xray-core/common/dice"
) )
func pickString(arr []string) string { func pickString(arr []string) string {
@@ -13,7 +14,7 @@ func pickString(arr []string) string {
case 1: case 1:
return arr[0] return arr[0]
default: default:
return arr[rand.N(n)] return arr[dice.Roll(n)]
} }
} }
+2 -2
View File
@@ -3,11 +3,11 @@ package kcp
import ( import (
"context" "context"
"io" "io"
"math/rand/v2"
"sync/atomic" "sync/atomic"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
@@ -15,7 +15,7 @@ import (
"github.com/xtls/xray-core/transport/internet/tls" "github.com/xtls/xray-core/transport/internet/tls"
) )
var globalConv = uint32(uint16(rand.Uint32())) var globalConv = uint32(dice.RollUint16())
func fetchInput(_ context.Context, input io.Reader, reader PacketReader, conn *Connection) { func fetchInput(_ context.Context, input io.Reader, reader PacketReader, conn *Connection) {
cache := make(chan *buf.Buffer, 1024) cache := make(chan *buf.Buffer, 1024)
+2 -2
View File
@@ -7,13 +7,13 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"math/rand/v2"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
) )
@@ -182,7 +182,7 @@ func jitter(backoff time.Duration) time.Duration {
if half <= 0 { if half <= 0 {
return backoff return backoff
} }
return half + rand.N(half) return half + time.Duration(dice.RollInt63n(int64(half)))
} }
func rateLimited(payload []byte) bool { func rateLimited(payload []byte) bool {
+1 -1
View File
@@ -72,7 +72,7 @@ func (s *localStorage) Get(ctx context.Context, name string) ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
data, err := readFile(ctx, full) data, err := os.ReadFile(full)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return nil, errNotFound return nil, errNotFound
-12
View File
@@ -1,12 +0,0 @@
//go:build !windows
package xdrive
import (
"context"
"os"
)
func readFile(_ context.Context, name string) ([]byte, error) {
return os.ReadFile(name)
}
@@ -1,30 +0,0 @@
package xdrive
import (
"context"
"errors"
"os"
"time"
"golang.org/x/sys/windows"
)
// readFile retries os.ReadFile for up to 2 seconds on sharing violations:
// os.Rename (MoveFileEx) in Put holds the file with DELETE access, which
// os.ReadFile does not share, until a moment after its new name appears in List
func readFile(ctx context.Context, name string) ([]byte, error) {
deadline := time.Now().Add(2 * time.Second)
delay := time.Millisecond
for {
data, err := os.ReadFile(name)
if !errors.Is(err, windows.ERROR_SHARING_VIOLATION) || time.Now().Add(delay).After(deadline) {
return data, err
}
select {
case <-ctx.Done():
return nil, err
case <-time.After(delay):
}
delay = min(2*delay, 100*time.Millisecond)
}
}
@@ -1,57 +0,0 @@
package xdrive
import (
"context"
"errors"
"sync"
"testing"
"time"
"golang.org/x/sys/windows"
)
func TestLocalSharingViolation(t *testing.T) {
storage, err := newLocalStorage(t.TempDir())
if err != nil {
t.Fatalf("newLocalStorage: %v", err)
}
const name = "streams/session/c2s/000000000.seg"
if err := storage.Put(context.Background(), name, []byte("segment")); err != nil {
t.Fatalf("Put: %v", err)
}
full, err := storage.resolve(name)
if err != nil {
t.Fatalf("resolve: %v", err)
}
path, err := windows.UTF16PtrFromString(full)
if err != nil {
t.Fatalf("UTF16PtrFromString: %v", err)
}
// os.Rename holds the file like this until a moment after its new name
// appears
handle, err := windows.CreateFile(path, windows.DELETE,
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
nil, windows.OPEN_EXISTING, 0, 0)
if err != nil {
t.Fatalf("CreateFile: %v", err)
}
var once sync.Once
release := func() { once.Do(func() { windows.CloseHandle(handle) }) }
defer release()
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
if _, err := storage.Get(ctx, name); !errors.Is(err, windows.ERROR_SHARING_VIOLATION) {
t.Fatalf("Get returned %v while the file is held, want a sharing violation", err)
}
time.AfterFunc(100*time.Millisecond, release)
data, err := storage.Get(context.Background(), name)
if err != nil {
t.Fatalf("Get: %v", err)
}
if string(data) != "segment" {
t.Fatalf("Get returned %q, want %q", data, "segment")
}
}