Legal

Open Source Licences

The distributed binary contains two MPL-2.0 components, each with one modified file (REALITY additionally carries the dependency-declaration changes that follow from its edit).

MPL-2.0 is file-level copyleft: no proprietary source need be published. The duties are to declare the components, state that they were modified, and let recipients obtain the source of the modified files. Unmodified portions are satisfied by the upstream public repositories at the anchors below.

Version anchors

ComponentUpstreamAnchorOur modification
Xray-corehttps://github.com/XTLS/Xray-coretag v26.7.28 (commit 5ca6f4b7d4dc)infra/conf/shadowsocks.go — Shadowsocks-2022 support removed
REALITYhttps://github.com/XTLS/REALITYcommit 9234c772ba8ftls.go — rate-limit dependency replaced by an inline token bucket (with the corresponding go.mod / go.sum removals)

本产品包含以 MPL-2.0 授权的 Xray-core 与 REALITY 组件,我们修改了其中各一个源文件。未修改部分的源码可在上表的上游仓库、对应版本锚点处获取;我们的修改以下方 patch 形式完整提供。

This product includes Xray-core and REALITY under the MPL-2.0. One source file in each was modified by us. Unmodified sources are available from the upstream repositories at the versions listed above; our modifications are provided in full as the patches below.

Patch 1 of 2 — Xray-core (MPL-2.0)

File: infra/conf/shadowsocks.go · Baseline: v26.7.28 (5ca6f4b7d4dc)

Download xray-core-conf-no-ss2022.patch

diff --git a/infra/conf/shadowsocks.go b/infra/conf/shadowsocks.go
index 18451ab..49fa7a2 100644
--- a/infra/conf/shadowsocks.go
+++ b/infra/conf/shadowsocks.go
@@ -3,17 +3,22 @@ package conf
 import (
 	"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/protocol"
 	"github.com/xtls/xray-core/common/serial"
 	"github.com/xtls/xray-core/common/task"
 	"github.com/xtls/xray-core/proxy/shadowsocks"
-	"github.com/xtls/xray-core/proxy/shadowsocks_2022"
 	"google.golang.org/protobuf/proto"
 )
 
+// isShadowsocks2022Cipher reports whether the cipher names a Shadowsocks
+// 2022 suite. This build strips SS2022 (its implementation pulls the
+// GPL-3.0 sagernet/sing stack); the check keeps the error explicit
+// instead of falling through to the legacy-cipher parser.
+func isShadowsocks2022Cipher(c string) bool {
+	return strings.HasPrefix(strings.ToLower(c), "2022-blake3-")
+}
+
 func cipherFromString(c string) shadowsocks.CipherType {
 	switch strings.ToLower(c) {
 	case "aes-128-gcm", "aead_aes_128_gcm":
@@ -55,8 +60,8 @@ func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
 		v.Users = v.Clients
 	}
 
-	if C.Contains(shadowaead_2022.List, v.Cipher) {
-		return buildShadowsocks2022(v)
+	if isShadowsocks2022Cipher(v.Cipher) {
+		return nil, errors.New("shadowsocks 2022 is not supported in this build")
 	}
 
 	config := new(shadowsocks.ServerConfig)
@@ -110,72 +115,6 @@ func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
 	return config, nil
 }
 
-func buildShadowsocks2022(v *ShadowsocksServerConfig) (proto.Message, error) {
-	if len(v.Users) == 0 {
-		config := new(shadowsocks_2022.ServerConfig)
-		config.Method = v.Cipher
-		config.Key = v.Password
-		config.Network = v.NetworkList.Build()
-		config.Email = v.Email
-		return config, nil
-	}
-
-	if v.Cipher == "" {
-		return nil, errors.New("shadowsocks 2022 (multi-user): missing server method")
-	}
-	if !strings.Contains(v.Cipher, "aes") {
-		return nil, errors.New("shadowsocks 2022 (multi-user): only blake3-aes-*-gcm methods are supported")
-	}
-
-	if v.Users[0].Address == nil {
-		config := new(shadowsocks_2022.MultiUserServerConfig)
-		config.Method = v.Cipher
-		config.Key = v.Password
-		config.Network = v.NetworkList.Build()
-
-		config.Users = make([]*protocol.User, len(v.Users))
-		processUser := func(idx int) error {
-			user := v.Users[idx]
-			if user.Cipher != "" {
-				return errors.New("shadowsocks 2022 (multi-user): users must have empty method")
-			}
-			account := &shadowsocks_2022.Account{
-				Key: user.Password,
-			}
-			config.Users[idx] = &protocol.User{
-				Email:   user.Email,
-				Level:   uint32(user.Level),
-				Account: serial.ToTypedMessage(account),
-			}
-			return nil
-		}
-		if err := task.ParallelForN(len(v.Users), processUser); err != nil {
-			return nil, err
-		}
-		return config, nil
-	}
-
-	config := new(shadowsocks_2022.RelayServerConfig)
-	config.Method = v.Cipher
-	config.Key = v.Password
-	config.Network = v.NetworkList.Build()
-	for _, user := range v.Users {
-		if user.Cipher != "" {
-			return nil, errors.New("shadowsocks 2022 (relay): users must have empty method")
-		}
-		if user.Address == nil {
-			return nil, errors.New("shadowsocks 2022 (relay): all users must have relay address")
-		}
-		config.Destinations = append(config.Destinations, &shadowsocks_2022.RelayDestination{
-			Key:     user.Password,
-			Email:   user.Email,
-			Address: user.Address.Build(),
-			Port:    uint32(user.Port),
-		})
-	}
-	return config, nil
-}
-
 type ShadowsocksServerTarget struct {
 	Address  *Address `json:"address"`
 	Port     uint16   `json:"port"`
@@ -216,30 +155,15 @@ func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
 
 	if len(v.Servers) == 1 {
 		server := v.Servers[0]
-		if C.Contains(shadowaead_2022.List, server.Cipher) {
-			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.")
-			}
-
-			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 isShadowsocks2022Cipher(server.Cipher) {
+			return nil, errors.New("shadowsocks 2022 is not supported in this build")
 		}
 	}
 
 	config := new(shadowsocks.ClientConfig)
 	for _, server := range v.Servers {
-		if C.Contains(shadowaead_2022.List, server.Cipher) {
-			return nil, errors.New("Shadowsocks 2022 accept no multi servers")
+		if isShadowsocks2022Cipher(server.Cipher) {
+			return nil, errors.New("shadowsocks 2022 is not supported in this build")
 		}
 		if server.Address == nil {
 			return nil, errors.New("Shadowsocks server address is not set.")

Patch 2 of 2 — REALITY (MPL-2.0)

Files: tls.go (with go.mod / go.sum) · Baseline: commit 9234c772ba8f

Download reality-tls-token-bucket.patch

diff --git a/go.mod b/go.mod
index 22f8f90..c906155 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,6 @@ go 1.24.0
 
 require (
 	github.com/cloudflare/circl v1.6.3
-	github.com/juju/ratelimit v1.0.2
 	github.com/pires/go-proxyproto v0.11.0
 	github.com/refraction-networking/utls v1.8.2
 	golang.org/x/crypto v0.48.0
@@ -14,5 +13,4 @@ require (
 require (
 	github.com/andybalholm/brotli v1.0.6 // indirect
 	github.com/klauspost/compress v1.17.4 // indirect
-	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
 )
diff --git a/go.sum b/go.sum
index 72fddb4..d4f64e7 100644
--- a/go.sum
+++ b/go.sum
@@ -2,15 +2,8 @@ github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sx
 github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
 github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
 github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
-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/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
 github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
-github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
-github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
 github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
 github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
@@ -19,5 +12,3 @@ golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
 golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
 golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
 golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
-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=
diff --git a/tls.go b/tls.go
index 4c8ef86..e85afd7 100644
--- a/tls.go
+++ b/tls.go
@@ -50,7 +50,6 @@ import (
 	"sync"
 	"time"
 
-	"github.com/juju/ratelimit"
 	"github.com/pires/go-proxyproto"
 	"golang.org/x/crypto/curve25519"
 	"golang.org/x/crypto/hkdf"
@@ -101,10 +100,52 @@ func (c *MirrorConn) SetWriteDeadline(t time.Time) error {
 	return nil
 }
 
+// tokenBucket is a license-clean replacement for the LGPL-3.0
+// github.com/juju/ratelimit dependency. It implements exactly the
+// NewBucketWithRate/Wait(count) semantics the fallback shaper below uses:
+// tokens refill continuously at rate per second up to capacity, Wait takes
+// count tokens immediately (going into debt if needed) and sleeps until the
+// debt would be repaid.
+type tokenBucket struct {
+	mu       sync.Mutex
+	rate     float64
+	capacity int64
+	tokens   float64
+	last     time.Time
+}
+
+func newTokenBucketWithRate(rate float64, capacity int64) *tokenBucket {
+	return &tokenBucket{
+		rate:     rate,
+		capacity: capacity,
+		tokens:   float64(capacity),
+		last:     time.Now(),
+	}
+}
+
+func (b *tokenBucket) Wait(count int64) {
+	b.mu.Lock()
+	now := time.Now()
+	b.tokens += now.Sub(b.last).Seconds() * b.rate
+	if b.tokens > float64(b.capacity) {
+		b.tokens = float64(b.capacity)
+	}
+	b.last = now
+	b.tokens -= float64(count)
+	var wait time.Duration
+	if b.tokens < 0 && b.rate > 0 {
+		wait = time.Duration(-b.tokens / b.rate * float64(time.Second))
+	}
+	b.mu.Unlock()
+	if wait > 0 {
+		time.Sleep(wait)
+	}
+}
+
 type RatelimitedConn struct {
 	net.Conn
 	After  int64
-	Bucket *ratelimit.Bucket
+	Bucket *tokenBucket
 }
 
 func (c *RatelimitedConn) Read(b []byte) (int, error) {
@@ -132,7 +173,7 @@ func NewRatelimitedConn(conn net.Conn, limit *LimitFallback) net.Conn {
 	return &RatelimitedConn{
 		Conn:   conn,
 		After:  int64(limit.AfterBytes),
-		Bucket: ratelimit.NewBucketWithRate(float64(limit.BytesPerSec), int64(burstBytesPerSec)),
+		Bucket: newTokenBucketWithRate(float64(limit.BytesPerSec), int64(burstBytesPerSec)),
 	}
 }
 

Self-verification offered by the delivering side

git clone --depth 1 --branch v26.7.28 https://github.com/XTLS/Xray-core.git xraycore-src
git clone https://github.com/XTLS/REALITY.git reality-src && git -C reality-src checkout 9234c772ba8f
git -C xraycore-src apply xray-core-conf-no-ss2022.patch
git -C reality-src  apply reality-tls-token-bucket.patch

Both applying cleanly means the material is complete.