diff --git a/config/minecraft/client/config.json b/config/minecraft/client/config.json new file mode 100644 index 0000000..0b14eee --- /dev/null +++ b/config/minecraft/client/config.json @@ -0,0 +1,36 @@ +{ + "log": { + "level": "info" + }, + "inbounds": [ + { + "type": "mixed", + "tag": "mixed-in", + "listen": "127.0.0.1", + "listen_port": 1080 + } + ], + "outbounds": [ + { + "type": "minecraft", + "tag": "mc-out", + "server": "127.0.0.1", + "server_port": 25565, + "username": "Steve", + "password": "my-secret-password" + }, + { + "type": "direct", + "tag": "direct" + } + ], + "route": { + "rules": [ + { + "inbound": "mixed-in", + "outbound": "mc-out" + } + ], + "final": "direct" + } +} diff --git a/config/minecraft/server/config.json b/config/minecraft/server/config.json new file mode 100644 index 0000000..48bc612 --- /dev/null +++ b/config/minecraft/server/config.json @@ -0,0 +1,62 @@ +{ + "log": { + "level": "info" + }, + "inbounds": [ + { + "type": "minecraft", + "tag": "mc-in", + "listen": "::", + "listen_port": 25565, + "users": [ + { + "username": "Steve", + "password": "my-secret-password" + } + ], + "status": { + "forward": false, + "version": { + "name": "1.20.4", + "protocol": 765 + }, + "description": {"text": "A Minecraft Server", "color": "green"}, + "players": { + "max": 20, + "online": 1, + "sample": [ + { + "name": "Notch", + "id": "069a79f4-44e9-4726-a5be-fca90e38aaf5" + } + ] + }, + "enforces_secure_chat": false + }, + "fallback": { + "server": "mc.hypixel.net", + "server_port": 25565, + "tag": "mc-fallback" + } + } + ], + "outbounds": [ + { + "type": "direct", + "tag": "proxy-out" + }, + { + "type": "direct", + "tag": "fallback-out" + } + ], + "route": { + "rules": [ + { + "inbound": "mc-fallback", + "outbound": "fallback-out" + } + ], + "final": "proxy-out" + } +} diff --git a/works/patch/constant/proxy.go.patch b/works/patch/constant/proxy.go.patch index d57313e..8ecb5f9 100644 --- a/works/patch/constant/proxy.go.patch +++ b/works/patch/constant/proxy.go.patch @@ -1,20 +1,25 @@ --- a/constant/proxy.go +++ b/constant/proxy.go -@@ -31,6 +31,7 @@ +@@ -31,6 +31,8 @@ TypeCCM = "ccm" TypeOCM = "ocm" TypeOOMKiller = "oom-killer" + TypeMySQL = "mysql" // OMV ++ TypeMinecraft = "minecraft" // OMV ) const ( -@@ -88,6 +89,10 @@ +@@ -88,6 +90,14 @@ return "AnyTLS" case TypeTailscale: return "Tailscale" + // OMV start: register mysql type name + case TypeMySQL: + return "MySQL" ++ // OMV end ++ // OMV start: register minecraft type name ++ case TypeMinecraft: ++ return "Minecraft" + // OMV end case TypeSelector: return "Selector" diff --git a/works/patch/docs/configuration/inbound/minecraft.md.patch b/works/patch/docs/configuration/inbound/minecraft.md.patch new file mode 100644 index 0000000..49fc848 --- /dev/null +++ b/works/patch/docs/configuration/inbound/minecraft.md.patch @@ -0,0 +1,121 @@ +--- /dev/null ++++ b/docs/configuration/inbound/minecraft.md +@@ -0,0 +1,118 @@ ++### Structure ++ ++```json ++{ ++ "type": "minecraft", ++ "tag": "mc-in", ++ ++ ... // Listen Fields ++ ++ "users": [ ++ { ++ "username": "Steve", ++ "password": "my-secret-password" ++ } ++ ], ++ "status": { ++ "forward": false, ++ "version": { ++ "name": "1.20.4", ++ "protocol": 765 ++ }, ++ "description": "A Minecraft Server", ++ "players": { ++ "max": 20, ++ "online": 1, ++ "sample": [ ++ { ++ "name": "Notch", ++ "id": "069a79f4-44e9-4726-a5be-fca90e38aaf5" ++ } ++ ] ++ }, ++ "favicon": "data:image/png;base64,", ++ "enforces_secure_chat": false ++ }, ++ "fallback": { ++ "server": "mc.example.com", ++ "server_port": 25565, ++ "tag": "mc-fallback" ++ } ++} ++``` ++ ++### Listen Fields ++ ++See [Listen Fields](/configuration/shared/listen/) for details. ++ ++### Fields ++ ++#### users ++ ++List of users allowed to authenticate through the proxy. Each entry has a `username` and `password`. ++ ++Connections from usernames not in this list are forwarded to the `fallback` server (if configured), allowing real Minecraft players to connect transparently. ++ ++#### status ++ ++Controls the server list ping response shown to Minecraft clients. ++ ++##### status.forward ++ ++If `true`, status pings are forwarded to the `fallback` server instead of being answered locally. Requires `fallback` to be configured. ++ ++##### status.version ++ ++The version info shown in the server list. ++ ++- `name`: Version string (e.g. `"1.20.4"`) ++- `protocol`: Protocol number (e.g. `765`). Defaults to `765` (1.20.4). ++ ++##### status.description ++ ++The MOTD shown in the server list. Accepts either a plain string or a [JSON Chat Component](https://wiki.vg/Text_formatting#JSON_text_component) object for rich formatting: ++ ++```json ++"description": "A plain text MOTD" ++``` ++ ++```json ++"description": {"text": "A colored MOTD", "color": "green"} ++``` ++ ++##### status.players ++ ++- `max`: Maximum player count shown. ++- `online`: Online player count shown. ++- `sample`: Player list shown on hover. Each entry has `name` and optionally `id` (UUID). If `id` is omitted, a deterministic offline UUID is generated from the name. ++ ++##### status.favicon ++ ++Server icon in `data:image/png;base64,` format, shown in the server list. ++ ++##### status.enforces_secure_chat ++ ++Whether the server enforces signed chat messages. Passed through to the client as-is. ++ ++#### fallback ++ ++If configured, connections from unknown users are proxied transparently to this Minecraft server via the sing-box router. ++ ++- `server`: Hostname or IP of the fallback server. ++- `server_port`: Port of the fallback server. Defaults to `25565`. ++- `tag`: When set, fallback connections are identified by this tag in routing metadata, allowing routing rules to send fallback traffic through a different outbound than proxy traffic. ++ ++### Description ++ ++The Minecraft inbound disguises proxy traffic as Minecraft Java Edition protocol traffic. ++ ++The connection flow is: ++ ++1. Client connects and sends a Minecraft handshake packet ++2. If the next state is **Status**: the server responds with a fake server list ping (or forwards to fallback if `status.forward` is enabled) ++3. If the next state is **Login**: the server performs the standard Minecraft encryption handshake (RSA key exchange + AES/CFB8) ++4. Authentication is verified by deriving a shared secret from `SHA-256(password + verify_token)[:16]`; real Minecraft clients naturally fail this check and receive an authentic "Failed to verify username!" error ++5. After login, smux multiplexing runs over the AES/CFB8 encrypted connection ++6. Each smux stream carries a 1-byte command (`0x01` for TCP, `0x03` for UDP) followed by a SOCKS address header ++ ++Legacy pre-1.7 pings (`0xFE`) are also handled and return a correctly formatted response. diff --git a/works/patch/docs/configuration/inbound/minecraft.zh.md.patch b/works/patch/docs/configuration/inbound/minecraft.zh.md.patch new file mode 100644 index 0000000..62bd05f --- /dev/null +++ b/works/patch/docs/configuration/inbound/minecraft.zh.md.patch @@ -0,0 +1,121 @@ +--- /dev/null ++++ b/docs/configuration/inbound/minecraft.zh.md +@@ -0,0 +1,118 @@ ++### 结构 ++ ++```json ++{ ++ "type": "minecraft", ++ "tag": "mc-in", ++ ++ ... // 监听字段 ++ ++ "users": [ ++ { ++ "username": "Steve", ++ "password": "my-secret-password" ++ } ++ ], ++ "status": { ++ "forward": false, ++ "version": { ++ "name": "1.20.4", ++ "protocol": 765 ++ }, ++ "description": "A Minecraft Server", ++ "players": { ++ "max": 20, ++ "online": 1, ++ "sample": [ ++ { ++ "name": "Notch", ++ "id": "069a79f4-44e9-4726-a5be-fca90e38aaf5" ++ } ++ ] ++ }, ++ "favicon": "data:image/png;base64,", ++ "enforces_secure_chat": false ++ }, ++ "fallback": { ++ "server": "mc.example.com", ++ "server_port": 25565, ++ "tag": "mc-fallback" ++ } ++} ++``` ++ ++### 监听字段 ++ ++详见 [监听字段](/configuration/shared/listen/)。 ++ ++### 字段 ++ ++#### users ++ ++允许通过代理认证的用户列表,每项包含 `username` 和 `password`。 ++ ++不在列表中的用户名连接将被透明转发至 `fallback` 服务器(如已配置),使真实 Minecraft 玩家可以正常连接。 ++ ++#### status ++ ++控制向 Minecraft 客户端返回的服务器列表 ping 响应。 ++ ++##### status.forward ++ ++若为 `true`,status ping 将转发至 `fallback` 服务器而非本地响应。需要配置 `fallback`。 ++ ++##### status.version ++ ++服务器列表中显示的版本信息。 ++ ++- `name`:版本字符串(如 `"1.20.4"`) ++- `protocol`:协议号(如 `765`)。默认为 `765`(即 1.20.4)。 ++ ++##### status.description ++ ++服务器列表中显示的 MOTD。支持纯文本字符串或 [JSON Chat Component](https://wiki.vg/Text_formatting#JSON_text_component) 对象以实现富文本格式: ++ ++```json ++"description": "纯文本 MOTD" ++``` ++ ++```json ++"description": {"text": "带颜色的 MOTD", "color": "green"} ++``` ++ ++##### status.players ++ ++- `max`:显示的最大玩家数。 ++- `online`:显示的在线玩家数。 ++- `sample`:鼠标悬停时显示的玩家列表,每项包含 `name` 和可选的 `id`(UUID)。若省略 `id`,将根据用户名生成确定性离线 UUID。 ++ ++##### status.favicon ++ ++服务器图标,格式为 `data:image/png;base64,`,显示在服务器列表中。 ++ ++##### status.enforces_secure_chat ++ ++是否要求客户端使用签名聊天消息,将原样传递给客户端。 ++ ++#### fallback ++ ++配置后,未知用户的连接将通过 sing-box 路由器透明代理至此 Minecraft 服务器。 ++ ++- `server`:fallback 服务器的主机名或 IP。 ++- `server_port`:fallback 服务器端口,默认为 `25565`。 ++- `tag`:设置后,fallback 连接在路由元数据中使用该标识,允许通过路由规则将 fallback 流量与代理流量分别发往不同出站。 ++ ++### 说明 ++ ++Minecraft 入站将代理流量伪装为 Minecraft Java Edition 协议流量。 ++ ++连接流程: ++ ++1. 客户端连接并发送 Minecraft 握手包 ++2. 若下一状态为 **Status**:服务端返回伪造的服务器列表 ping 响应(若启用了 `status.forward` 则转发至 fallback) ++3. 若下一状态为 **Login**:服务端执行标准 Minecraft 加密握手(RSA 密钥交换 + AES/CFB8) ++4. 认证通过 `SHA-256(password + verify_token)[:16]` 派生共享密钥进行验证;真实 Minecraft 客户端会自然失败,并收到真实的"Failed to verify username!"错误 ++5. 登录完成后,smux 多路复用在 AES/CFB8 加密连接上运行 ++6. 每个 smux 流携带 1 字节命令(`0x01` 表示 TCP,`0x03` 表示 UDP)加 SOCKS 地址头 ++ ++同时也支持 1.7 之前的旧版 ping(`0xFE`),并返回格式正确的响应。 diff --git a/works/patch/docs/configuration/omv.zh.md.patch b/works/patch/docs/configuration/omv.zh.md.patch index 6285c63..58a1aee 100644 --- a/works/patch/docs/configuration/omv.zh.md.patch +++ b/works/patch/docs/configuration/omv.zh.md.patch @@ -1,8 +1,6 @@ --- /dev/null +++ b/docs/configuration/omv.zh.md -@@ -0,0 +1,57 @@ -+// OMV -+ +@@ -0,0 +1,55 @@ +# OMV 改动说明 + +本页面记录了 OMV(omv-dijiang)在上游 sing-box 基础上所做的全部改动。 diff --git a/works/patch/docs/configuration/outbound/minecraft.md.patch b/works/patch/docs/configuration/outbound/minecraft.md.patch new file mode 100644 index 0000000..72cb06d --- /dev/null +++ b/works/patch/docs/configuration/outbound/minecraft.md.patch @@ -0,0 +1,55 @@ +--- /dev/null ++++ b/docs/configuration/outbound/minecraft.md +@@ -0,0 +1,52 @@ ++### Structure ++ ++```json ++{ ++ "type": "minecraft", ++ "tag": "mc-out", ++ ++ "server": "mc.example.com", ++ "server_port": 25565, ++ "username": "Steve", ++ "password": "my-secret-password", ++ ++ ... // Dialer Fields ++} ++``` ++ ++### Dialer Fields ++ ++See [Dialer Fields](/configuration/shared/dialer/) for details. ++ ++### Fields ++ ++#### server ++ ++==Required== ++ ++The Minecraft server address. ++ ++#### server_port ++ ++The Minecraft server port. Defaults to `25565`. ++ ++#### username ++ ++The username to authenticate with. Defaults to `Steve`. ++ ++#### password ++ ++The password to authenticate with. ++ ++### Description ++ ++The Minecraft outbound connects to a Minecraft inbound and tunnels traffic through the Minecraft Java Edition protocol. ++ ++The connection flow is: ++ ++1. Outbound connects to the server and sends a Minecraft handshake + login start ++2. Server responds with an encryption request (RSA public key + verify token) ++3. Outbound derives the shared secret from `SHA-256(password + verify_token)[:16]` and sends an encryption response ++4. Both sides switch to AES/CFB8 encryption using the derived shared secret ++5. After receiving login success, smux multiplexing is established over the encrypted connection ++6. Each smux stream carries a 1-byte command (`0x01` for TCP, `0x03` for UDP) followed by a SOCKS address header diff --git a/works/patch/docs/configuration/outbound/minecraft.zh.md.patch b/works/patch/docs/configuration/outbound/minecraft.zh.md.patch new file mode 100644 index 0000000..a8e08e3 --- /dev/null +++ b/works/patch/docs/configuration/outbound/minecraft.zh.md.patch @@ -0,0 +1,55 @@ +--- /dev/null ++++ b/docs/configuration/outbound/minecraft.zh.md +@@ -0,0 +1,52 @@ ++### 结构 ++ ++```json ++{ ++ "type": "minecraft", ++ "tag": "mc-out", ++ ++ "server": "mc.example.com", ++ "server_port": 25565, ++ "username": "Steve", ++ "password": "my-secret-password", ++ ++ ... // 拨号字段 ++} ++``` ++ ++### 拨号字段 ++ ++详见 [拨号字段](/configuration/shared/dialer/)。 ++ ++### 字段 ++ ++#### server ++ ++==必填== ++ ++Minecraft 服务器地址。 ++ ++#### server_port ++ ++Minecraft 服务器端口,默认为 `25565`。 ++ ++#### username ++ ++认证使用的用户名,默认为 `Steve`。 ++ ++#### password ++ ++认证使用的密码。 ++ ++### 说明 ++ ++Minecraft 出站连接至 Minecraft 入站,通过 Minecraft Java Edition 协议隧道传输流量。 ++ ++连接流程: ++ ++1. 出站连接至服务器,发送 Minecraft 握手包和登录开始包 ++2. 服务端回复加密请求(RSA 公钥 + 验证令牌) ++3. 出站通过 `SHA-256(password + verify_token)[:16]` 派生共享密钥,发送加密响应 ++4. 双方切换至使用派生共享密钥的 AES/CFB8 加密 ++5. 收到登录成功后,在加密连接上建立 smux 多路复用 ++6. 每个 smux 流携带 1 字节命令(`0x01` 表示 TCP,`0x03` 表示 UDP)加 SOCKS 地址头 diff --git a/works/patch/include/registry.go.patch b/works/patch/include/registry.go.patch index 83cb531..bf319f9 100644 --- a/works/patch/include/registry.go.patch +++ b/works/patch/include/registry.go.patch @@ -1,26 +1,29 @@ --- a/include/registry.go +++ b/include/registry.go -@@ -23,6 +23,7 @@ +@@ -23,6 +23,8 @@ "github.com/sagernet/sing-box/protocol/group" "github.com/sagernet/sing-box/protocol/http" "github.com/sagernet/sing-box/protocol/mixed" ++ "github.com/sagernet/sing-box/protocol/minecraft" // OMV + "github.com/sagernet/sing-box/protocol/mysql" // OMV "github.com/sagernet/sing-box/protocol/naive" "github.com/sagernet/sing-box/protocol/redirect" "github.com/sagernet/sing-box/protocol/shadowsocks" -@@ -62,6 +63,7 @@ +@@ -62,6 +64,8 @@ shadowtls.RegisterInbound(registry) vless.RegisterInbound(registry) anytls.RegisterInbound(registry) + mysql.RegisterInbound(registry) // OMV ++ minecraft.RegisterInbound(registry) // OMV registerQUICInbounds(registry) registerStubForRemovedInbounds(registry) -@@ -90,6 +92,7 @@ +@@ -90,6 +94,8 @@ shadowtls.RegisterOutbound(registry) vless.RegisterOutbound(registry) anytls.RegisterOutbound(registry) + mysql.RegisterOutbound(registry) // OMV ++ minecraft.RegisterOutbound(registry) // OMV registerQUICOutbounds(registry) registerStubForRemovedOutbounds(registry) diff --git a/works/patch/option/minecraft.go.patch b/works/patch/option/minecraft.go.patch new file mode 100644 index 0000000..cd55442 --- /dev/null +++ b/works/patch/option/minecraft.go.patch @@ -0,0 +1,56 @@ +--- /dev/null ++++ b/option/minecraft.go +@@ -0,0 +1,53 @@ ++// OMV ++package option ++ ++import "encoding/json" ++ ++type MinecraftInboundOptions struct { ++ ListenOptions ++ Users []MinecraftUser `json:"users,omitempty"` ++ Status *MinecraftStatus `json:"status,omitempty"` ++ Fallback *MinecraftFallbackOptions `json:"fallback,omitempty"` ++} ++ ++type MinecraftFallbackOptions struct { ++ ServerOptions ++ Tag string `json:"tag,omitempty"` ++} ++ ++type MinecraftStatus struct { ++ Forward bool `json:"forward,omitempty"` ++ Version *MinecraftStatusVersion `json:"version,omitempty"` ++ Description json.RawMessage `json:"description,omitempty"` ++ Players *MinecraftStatusPlayers `json:"players,omitempty"` ++ Favicon string `json:"favicon,omitempty"` ++ EnforcesSecureChat bool `json:"enforces_secure_chat,omitempty"` ++} ++ ++type MinecraftStatusVersion struct { ++ Name string `json:"name,omitempty"` ++ Protocol int `json:"protocol,omitempty"` ++} ++ ++type MinecraftStatusPlayers struct { ++ Max int `json:"max,omitempty"` ++ Online int `json:"online,omitempty"` ++ Sample []MinecraftStatusPlayerSample `json:"sample,omitempty"` ++} ++ ++type MinecraftStatusPlayerSample struct { ++ Name string `json:"name"` ++ ID string `json:"id"` ++} ++ ++type MinecraftUser struct { ++ Username string `json:"username,omitempty"` ++ Password string `json:"password,omitempty"` ++} ++ ++type MinecraftOutboundOptions struct { ++ DialerOptions ++ ServerOptions ++ Username string `json:"username,omitempty"` ++ Password string `json:"password,omitempty"` ++} diff --git a/works/patch/protocol/minecraft/inbound.go.patch b/works/patch/protocol/minecraft/inbound.go.patch new file mode 100644 index 0000000..e134e9e --- /dev/null +++ b/works/patch/protocol/minecraft/inbound.go.patch @@ -0,0 +1,462 @@ +--- /dev/null ++++ b/protocol/minecraft/inbound.go +@@ -0,0 +1,459 @@ ++// OMV ++package minecraft ++ ++import ( ++ "bufio" ++ "bytes" ++ "context" ++ "crypto/rsa" ++ "io" ++ "net" ++ "os" ++ ++ "github.com/sagernet/sing-box/adapter" ++ "github.com/sagernet/sing-box/adapter/inbound" ++ "github.com/sagernet/sing-box/common/listener" ++ "github.com/sagernet/sing-box/common/uot" ++ C "github.com/sagernet/sing-box/constant" ++ "github.com/sagernet/sing-box/log" ++ "github.com/sagernet/sing-box/option" ++ "github.com/sagernet/sing/common" ++ E "github.com/sagernet/sing/common/exceptions" ++ "github.com/sagernet/sing/common/logger" ++ M "github.com/sagernet/sing/common/metadata" ++ N "github.com/sagernet/sing/common/network" ++ "github.com/sagernet/sing/common/task" ++ "github.com/sagernet/smux" ++) ++ ++func RegisterInbound(registry *inbound.Registry) { ++ inbound.Register[option.MinecraftInboundOptions](registry, C.TypeMinecraft, NewInbound) ++} ++ ++var _ adapter.TCPInjectableInbound = (*Inbound)(nil) ++ ++type Inbound struct { ++ inbound.Adapter ++ router adapter.ConnectionRouterEx ++ logger logger.ContextLogger ++ listener *listener.Listener ++ privateKey *rsa.PrivateKey ++ publicKey []byte ++ users map[string]string // username -> password ++ status option.MinecraftStatus ++ fallbackAddr M.Socksaddr ++ fallbackTag string ++} ++ ++func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.MinecraftInboundOptions) (adapter.Inbound, error) { ++ privateKey, err := generateRSAKeyPair() ++ if err != nil { ++ return nil, E.Cause(err, "generate RSA key pair") ++ } ++ ++ publicKeyBytes, err := marshalPublicKey(&privateKey.PublicKey) ++ if err != nil { ++ return nil, E.Cause(err, "marshal public key") ++ } ++ ++ users := make(map[string]string) ++ for _, user := range options.Users { ++ users[user.Username] = user.Password ++ } ++ ++ var status option.MinecraftStatus ++ if options.Status != nil { ++ status = *options.Status ++ } ++ ++ h := &Inbound{ ++ Adapter: inbound.NewAdapter(C.TypeMinecraft, tag), ++ router: uot.NewRouter(router, logger), ++ logger: logger, ++ privateKey: privateKey, ++ publicKey: publicKeyBytes, ++ users: users, ++ status: status, ++ } ++ ++ if options.Fallback != nil { ++ h.fallbackAddr = options.Fallback.ServerOptions.Build() ++ if h.fallbackAddr.Port == 0 { ++ h.fallbackAddr.Port = 25565 ++ } ++ h.fallbackTag = options.Fallback.Tag ++ } ++ ++ h.listener = listener.New(listener.Options{ ++ Context: ctx, ++ Logger: logger, ++ Network: []string{N.NetworkTCP}, ++ Listen: options.ListenOptions, ++ ConnectionHandler: h, ++ }) ++ return h, nil ++} ++ ++func (h *Inbound) Start(stage adapter.StartStage) error { ++ if stage != adapter.StartStateStart { ++ return nil ++ } ++ return h.listener.Start() ++} ++ ++func (h *Inbound) Close() error { ++ return common.Close(h.listener) ++} ++ ++func (h *Inbound) hasFallback() bool { ++ return h.fallbackAddr.IsValid() ++} ++ ++func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { ++ err := h.handleConnection(ctx, conn, metadata, onClose) ++ if err != nil && !E.IsClosed(err) { ++ h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) ++ } ++} ++ ++func (h *Inbound) handleConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { ++ br := bufio.NewReader(conn) ++ ++ firstByte, err := br.Peek(1) ++ if err != nil { ++ N.CloseOnHandshakeFailure(conn, onClose, err) ++ return E.Cause(err, "peek first byte") ++ } ++ ++ // Legacy ping (0xFE): forward or respond locally ++ if firstByte[0] == legacyPingByte { ++ if h.status.Forward && h.hasFallback() { ++ return h.doFallback(ctx, conn, br, nil, metadata, onClose) ++ } ++ return h.handleLegacyPing(conn, onClose) ++ } ++ ++ // Read handshake ++ hsID, hsData, err := readPacket(br) ++ if err != nil { ++ N.CloseOnHandshakeFailure(conn, onClose, err) ++ return E.Cause(err, "read handshake") ++ } ++ if hsID != packetHandshake { ++ N.CloseOnHandshakeFailure(conn, onClose, nil) ++ return E.New("expected handshake packet, got ", hsID) ++ } ++ ++ handshake, err := readHandshake(hsData) ++ if err != nil { ++ N.CloseOnHandshakeFailure(conn, onClose, err) ++ return E.Cause(err, "parse handshake") ++ } ++ ++ switch handshake.NextState { ++ case stateStatus: ++ if h.status.Forward && h.hasFallback() { ++ return h.doFallback(ctx, conn, br, []rawPacket{{hsID, hsData}}, metadata, onClose) ++ } ++ return h.handleStatus(br, conn, onClose) ++ case stateLogin: ++ return h.handleLogin(ctx, br, conn, metadata, onClose, hsID, hsData) ++ default: ++ N.CloseOnHandshakeFailure(conn, onClose, nil) ++ return E.New("unknown next state: ", handshake.NextState) ++ } ++} ++ ++// rawPacket holds a consumed MC packet (id + payload) for replay ++type rawPacket struct { ++ id int32 ++ data []byte ++} ++ ++// doFallback replays consumed packets and relays the connection to the fallback MC server ++// via the sing-box router (so routing rules / outbounds apply). ++func (h *Inbound) doFallback(ctx context.Context, conn net.Conn, br *bufio.Reader, consumed []rawPacket, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { ++ var prefix bytes.Buffer ++ for _, pkt := range consumed { ++ writePacket(&prefix, pkt.id, pkt.data) ++ } ++ ++ if h.fallbackTag != "" { ++ metadata.Inbound = h.fallbackTag ++ } else { ++ metadata.Inbound = h.Tag() ++ } ++ metadata.InboundType = h.Type() ++ metadata.Destination = h.fallbackAddr ++ ++ h.logger.InfoContext(ctx, "fallback connection to ", h.fallbackAddr) ++ h.router.RouteConnectionEx(ctx, newPrefixConn(conn, prefix.Bytes(), br), metadata, onClose) ++ return nil ++} ++ ++func (h *Inbound) handleLegacyPing(conn net.Conn, onClose N.CloseHandlerFunc) error { ++ defer func() { ++ conn.Close() ++ if onClose != nil { ++ onClose(nil) ++ } ++ }() ++ resp := encodeLegacyPingResponse(&h.status) ++ _, err := conn.Write(resp) ++ return err ++} ++ ++func (h *Inbound) handleStatus(br *bufio.Reader, conn net.Conn, onClose N.CloseHandlerFunc) error { ++ defer func() { ++ conn.Close() ++ if onClose != nil { ++ onClose(nil) ++ } ++ }() ++ ++ packetID, _, err := readPacket(br) ++ if err != nil { ++ return E.Cause(err, "read status request") ++ } ++ if packetID != packetStatusRequest { ++ return E.New("expected status request, got ", packetID) ++ } ++ ++ responseData := encodeStatusResponse(&h.status) ++ if err := writePacket(conn, packetStatusResponse, responseData); err != nil { ++ return E.Cause(err, "write status response") ++ } ++ ++ packetID, data, err := readPacket(br) ++ if err != nil { ++ return nil ++ } ++ if packetID != packetPingRequest { ++ return nil ++ } ++ return writePacket(conn, packetPingResponse, data) ++} ++ ++func (h *Inbound) handleLogin(ctx context.Context, br *bufio.Reader, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc, hsID int32, hsData []byte) error { ++ // Read Login Start ++ lsID, lsData, err := readPacket(br) ++ if err != nil { ++ N.CloseOnHandshakeFailure(conn, onClose, err) ++ return E.Cause(err, "read login start") ++ } ++ if lsID != packetLoginStart { ++ N.CloseOnHandshakeFailure(conn, onClose, nil) ++ return E.New("expected login start, got ", lsID) ++ } ++ ++ loginStart, err := readLoginStart(lsData) ++ if err != nil { ++ N.CloseOnHandshakeFailure(conn, onClose, err) ++ return E.Cause(err, "parse login start") ++ } ++ ++ username := loginStart.Name ++ ++ password, ok := h.users[username] ++ if !ok { ++ if h.hasFallback() { ++ return h.doFallback(ctx, conn, br, []rawPacket{ ++ {hsID, hsData}, ++ {lsID, lsData}, ++ }, metadata, onClose) ++ } ++ h.sendDisconnect(conn, "Failed to verify username!") ++ N.CloseOnHandshakeFailure(conn, onClose, nil) ++ return E.New("unknown user: ", username) ++ } ++ ++ // Generate verify token ++ verifyToken, err := generateVerifyToken() ++ if err != nil { ++ N.CloseOnHandshakeFailure(conn, onClose, err) ++ return E.Cause(err, "generate verify token") ++ } ++ ++ // Send Encryption Request ++ encReqData := encodeEncryptionRequest("", h.publicKey, verifyToken) ++ if err := writePacket(conn, packetEncryptionRequest, encReqData); err != nil { ++ N.CloseOnHandshakeFailure(conn, onClose, err) ++ return E.Cause(err, "write encryption request") ++ } ++ ++ // Read Encryption Response (via br to drain any buffered data) ++ packetID, data, err := readPacket(br) ++ if err != nil { ++ N.CloseOnHandshakeFailure(conn, onClose, err) ++ return E.Cause(err, "read encryption response") ++ } ++ if packetID != packetEncryptionResponse { ++ N.CloseOnHandshakeFailure(conn, onClose, nil) ++ return E.New("expected encryption response, got ", packetID) ++ } ++ ++ encResp, err := readEncryptionResponse(data) ++ if err != nil { ++ N.CloseOnHandshakeFailure(conn, onClose, err) ++ return E.Cause(err, "parse encryption response") ++ } ++ ++ // Decrypt shared secret and verify token ++ sharedSecret, err := rsa.DecryptPKCS1v15(nil, h.privateKey, encResp.SharedSecret) ++ if err != nil { ++ h.sendDisconnect(conn, "Failed to verify username!") ++ N.CloseOnHandshakeFailure(conn, onClose, err) ++ return E.Cause(err, "decrypt shared secret") ++ } ++ ++ decryptedToken, err := rsa.DecryptPKCS1v15(nil, h.privateKey, encResp.VerifyToken) ++ if err != nil { ++ h.sendDisconnect(conn, "Failed to verify username!") ++ N.CloseOnHandshakeFailure(conn, onClose, err) ++ return E.Cause(err, "decrypt verify token") ++ } ++ ++ if !bytes.Equal(decryptedToken, verifyToken) { ++ h.sendDisconnect(conn, "Failed to verify username!") ++ N.CloseOnHandshakeFailure(conn, onClose, nil) ++ return E.New("verify token mismatch") ++ } ++ ++ expectedSecret := deriveSharedSecret(password, verifyToken) ++ if !bytes.Equal(sharedSecret, expectedSecret) { ++ h.sendDisconnect(conn, "Failed to verify username!") ++ N.CloseOnHandshakeFailure(conn, onClose, nil) ++ return E.New("authentication failed for user: ", username) ++ } ++ ++ // Enable encryption — wrap with readerConn so br's buffer is drained properly ++ encConn, err := newEncryptedConn(&readerConn{Conn: conn, reader: br}, sharedSecret) ++ if err != nil { ++ N.CloseOnHandshakeFailure(conn, onClose, err) ++ return E.Cause(err, "enable encryption") ++ } ++ ++ // Send Login Success (encrypted) ++ loginSuccessData := encodeLoginSuccess(username) ++ if err := writePacket(encConn, packetLoginSuccess, loginSuccessData); err != nil { ++ N.CloseOnHandshakeFailure(conn, onClose, err) ++ return E.Cause(err, "write login success") ++ } ++ ++ // Read Login Acknowledged (1.20.2+) ++ _, _, err = readPacket(encConn) ++ if err != nil { ++ N.CloseOnHandshakeFailure(conn, onClose, err) ++ return E.Cause(err, "read login acknowledged") ++ } ++ ++ h.logger.InfoContext(ctx, "Minecraft login completed for user ", username, " from ", metadata.Source) ++ ++ return h.handleMuxSession(ctx, encConn, metadata.Source, onClose, username) ++} ++ ++func (h *Inbound) sendDisconnect(conn net.Conn, reason string) { ++ _ = writePacket(conn, packetLoginDisconnect, encodeLoginDisconnect(reason)) ++} ++ ++func (h *Inbound) handleMuxSession(ctx context.Context, conn net.Conn, source M.Socksaddr, onClose N.CloseHandlerFunc, user string) error { ++ session, err := smux.Server(conn, smuxConfig()) ++ if err != nil { ++ if onClose != nil { ++ onClose(err) ++ } ++ return err ++ } ++ var group task.Group ++ group.Append0(func(_ context.Context) error { ++ for { ++ stream, sErr := session.AcceptStream() ++ if sErr != nil { ++ return sErr ++ } ++ go h.handleMuxStream(ctx, stream, source, user) ++ } ++ }) ++ group.Cleanup(func() { ++ session.Close() ++ if onClose != nil { ++ onClose(os.ErrClosed) ++ } ++ }) ++ return group.Run(ctx) ++} ++ ++func (h *Inbound) handleMuxStream(ctx context.Context, conn net.Conn, source M.Socksaddr, user string) { ++ err := h.handleMuxStream0(ctx, conn, source, user) ++ if err != nil { ++ h.logger.ErrorContext(ctx, E.Cause(err, "process mux stream")) ++ } ++} ++ ++func (h *Inbound) handleMuxStream0(ctx context.Context, conn net.Conn, source M.Socksaddr, user string) error { ++ var cmdBuf [1]byte ++ _, err := conn.Read(cmdBuf[:]) ++ if err != nil { ++ return E.Cause(err, "read command") ++ } ++ command := cmdBuf[0] ++ ++ destination, err := M.SocksaddrSerializer.ReadAddrPort(conn) ++ if err != nil { ++ return E.Cause(err, "read destination") ++ } ++ ++ var metadata adapter.InboundContext ++ metadata.Inbound = h.Tag() ++ metadata.InboundType = h.Type() ++ metadata.Source = source ++ metadata.User = user ++ ++ switch command { ++ case commandTCP: ++ metadata.Destination = destination ++ h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) ++ h.router.RouteConnectionEx(ctx, conn, metadata, nil) ++ case commandUDP: ++ metadata.Destination = destination ++ h.logger.InfoContext(ctx, "inbound UoT packet connection to ", metadata.Destination) ++ h.router.RouteConnectionEx(ctx, conn, metadata, nil) ++ default: ++ return E.New("unknown command ", command) ++ } ++ return nil ++} ++ ++// prefixConn replays prefix bytes, then drains remaining buffered data, then reads from conn. ++// Writes go directly to the underlying conn. ++type prefixConn struct { ++ net.Conn ++ reader io.Reader ++} ++ ++func newPrefixConn(conn net.Conn, prefix []byte, remaining io.Reader) *prefixConn { ++ var readers []io.Reader ++ if len(prefix) > 0 { ++ readers = append(readers, bytes.NewReader(prefix)) ++ } ++ readers = append(readers, remaining) ++ return &prefixConn{ ++ Conn: conn, ++ reader: io.MultiReader(readers...), ++ } ++} ++ ++func (c *prefixConn) Read(b []byte) (int, error) { ++ return c.reader.Read(b) ++} ++ ++// readerConn overrides Read to use a different io.Reader (e.g. a bufio.Reader) ++// while keeping all other net.Conn methods on the underlying conn. ++type readerConn struct { ++ net.Conn ++ reader io.Reader ++} ++ ++func (c *readerConn) Read(b []byte) (int, error) { ++ return c.reader.Read(b) ++} diff --git a/works/patch/protocol/minecraft/outbound.go.patch b/works/patch/protocol/minecraft/outbound.go.patch new file mode 100644 index 0000000..b9e591a --- /dev/null +++ b/works/patch/protocol/minecraft/outbound.go.patch @@ -0,0 +1,319 @@ +--- /dev/null ++++ b/protocol/minecraft/outbound.go +@@ -0,0 +1,316 @@ ++// OMV ++package minecraft ++ ++import ( ++ "context" ++ "crypto/rand" ++ "crypto/rsa" ++ "net" ++ "sync" ++ ++ "github.com/sagernet/sing-box/adapter" ++ "github.com/sagernet/sing-box/adapter/outbound" ++ "github.com/sagernet/sing-box/common/dialer" ++ C "github.com/sagernet/sing-box/constant" ++ "github.com/sagernet/sing-box/log" ++ "github.com/sagernet/sing-box/option" ++ "github.com/sagernet/sing/common" ++ E "github.com/sagernet/sing/common/exceptions" ++ "github.com/sagernet/sing/common/logger" ++ M "github.com/sagernet/sing/common/metadata" ++ N "github.com/sagernet/sing/common/network" ++ "github.com/sagernet/sing/common/uot" ++ "github.com/sagernet/smux" ++) ++ ++func RegisterOutbound(registry *outbound.Registry) { ++ outbound.Register[option.MinecraftOutboundOptions](registry, C.TypeMinecraft, NewOutbound) ++} ++ ++var _ adapter.InterfaceUpdateListener = (*Outbound)(nil) ++ ++type Outbound struct { ++ outbound.Adapter ++ ctx context.Context ++ logger logger.ContextLogger ++ dialer N.Dialer ++ serverAddr M.Socksaddr ++ username string ++ password string ++ ++ sessionAccess sync.Mutex ++ session *muxSession ++ nextSession uint32 ++} ++ ++type muxSession struct { ++ session *smux.Session ++ conn net.Conn ++} ++ ++func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.MinecraftOutboundOptions) (adapter.Outbound, error) { ++ outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) ++ if err != nil { ++ return nil, err ++ } ++ ++ ob := &Outbound{ ++ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeMinecraft, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), ++ ctx: ctx, ++ logger: logger, ++ dialer: outboundDialer, ++ serverAddr: options.ServerOptions.Build(), ++ username: options.Username, ++ password: options.Password, ++ } ++ ++ if ob.serverAddr.Port == 0 { ++ ob.serverAddr.Port = 25565 ++ } ++ ++ if ob.username == "" { ++ ob.username = "Steve" ++ } ++ ++ return ob, nil ++} ++ ++func (h *Outbound) createSession() (*muxSession, error) { ++ h.logger.InfoContext(h.ctx, "creating Minecraft session to ", h.serverAddr) ++ ++ conn, err := h.dialer.DialContext(h.ctx, N.NetworkTCP, h.serverAddr) ++ if err != nil { ++ return nil, E.Cause(err, "dial server") ++ } ++ ++ // Send Handshake ++ handshakeData := encodeHandshake(&handshakePacket{ ++ ProtocolVersion: protocolVersion, ++ ServerAddress: h.serverAddr.AddrString(), ++ ServerPort: h.serverAddr.Port, ++ NextState: stateLogin, ++ }) ++ if err := writePacket(conn, packetHandshake, handshakeData); err != nil { ++ conn.Close() ++ return nil, E.Cause(err, "write handshake") ++ } ++ ++ // Send Login Start ++ loginStartData := encodeLoginStart(h.username) ++ if err := writePacket(conn, packetLoginStart, loginStartData); err != nil { ++ conn.Close() ++ return nil, E.Cause(err, "write login start") ++ } ++ ++ // Read Encryption Request ++ packetID, data, err := readPacket(conn) ++ if err != nil { ++ conn.Close() ++ return nil, E.Cause(err, "read encryption request") ++ } ++ ++ // Check for disconnect ++ if packetID == packetLoginDisconnect { ++ conn.Close() ++ return nil, E.New("server disconnected during login") ++ } ++ ++ if packetID != packetEncryptionRequest { ++ conn.Close() ++ return nil, E.New("expected encryption request, got ", packetID) ++ } ++ ++ encReq, err := readEncryptionRequest(data) ++ if err != nil { ++ conn.Close() ++ return nil, E.Cause(err, "parse encryption request") ++ } ++ ++ // Parse server's public key ++ serverPubKey, err := parsePublicKey(encReq.PublicKey) ++ if err != nil { ++ conn.Close() ++ return nil, E.Cause(err, "parse server public key") ++ } ++ ++ // Derive shared secret from password + verify token ++ sharedSecret := deriveSharedSecret(h.password, encReq.VerifyToken) ++ ++ // Encrypt shared secret with server's public key ++ encryptedSecret, err := rsa.EncryptPKCS1v15(rand.Reader, serverPubKey, sharedSecret) ++ if err != nil { ++ conn.Close() ++ return nil, E.Cause(err, "encrypt shared secret") ++ } ++ ++ // Encrypt verify token with server's public key ++ encryptedToken, err := rsa.EncryptPKCS1v15(rand.Reader, serverPubKey, encReq.VerifyToken) ++ if err != nil { ++ conn.Close() ++ return nil, E.Cause(err, "encrypt verify token") ++ } ++ ++ // Send Encryption Response ++ encRespData := encodeEncryptionResponse(encryptedSecret, encryptedToken) ++ if err := writePacket(conn, packetEncryptionResponse, encRespData); err != nil { ++ conn.Close() ++ return nil, E.Cause(err, "write encryption response") ++ } ++ ++ // Enable encryption ++ encConn, err := newEncryptedConn(conn, sharedSecret) ++ if err != nil { ++ conn.Close() ++ return nil, E.Cause(err, "enable encryption") ++ } ++ ++ // Read Login Success ++ packetID, _, err = readPacket(encConn) ++ if err != nil { ++ conn.Close() ++ return nil, E.Cause(err, "read login success") ++ } ++ if packetID != packetLoginSuccess { ++ conn.Close() ++ return nil, E.New("expected login success, got ", packetID) ++ } ++ ++ // Send Login Acknowledged (packet ID 0x03, empty data) ++ if err := writePacket(encConn, 0x03, nil); err != nil { ++ conn.Close() ++ return nil, E.Cause(err, "write login acknowledged") ++ } ++ ++ // Create smux session over encrypted connection ++ session, err := smux.Client(encConn, smuxConfig()) ++ if err != nil { ++ conn.Close() ++ return nil, E.Cause(err, "create mux session") ++ } ++ ++ return &muxSession{session: session, conn: conn}, nil ++} ++ ++func (h *Outbound) getSession() (*smux.Session, error) { ++ h.sessionAccess.Lock() ++ defer h.sessionAccess.Unlock() ++ ++ if h.session != nil && !h.session.session.IsClosed() { ++ return h.session.session, nil ++ } ++ if h.session != nil { ++ _ = common.Close(h.session.session, h.session.conn) ++ h.session = nil ++ } ++ ++ entry, err := h.createSession() ++ if err != nil { ++ return nil, err ++ } ++ h.session = entry ++ ++ go func(session *smux.Session, conn net.Conn) { ++ <-session.CloseChan() ++ h.sessionAccess.Lock() ++ if h.session != nil && h.session.session == session { ++ h.session = nil ++ } ++ h.sessionAccess.Unlock() ++ _ = common.Close(session, conn) ++ }(entry.session, entry.conn) ++ ++ return entry.session, nil ++} ++ ++func (h *Outbound) invalidateSession(session *smux.Session) { ++ h.sessionAccess.Lock() ++ defer h.sessionAccess.Unlock() ++ ++ if h.session != nil && h.session.session == session { ++ _ = common.Close(h.session.session, h.session.conn) ++ h.session = nil ++ } ++} ++ ++func (h *Outbound) openStream(ctx context.Context, command byte, destination M.Socksaddr) (net.Conn, error) { ++ _ = ctx ++ // Only 1 session since server limits connections to 2 (we use 1) ++ for i := 0; i < 2; i++ { ++ session, err := h.getSession() ++ if err != nil { ++ return nil, err ++ } ++ ++ stream, err := session.OpenStream() ++ if err != nil { ++ h.invalidateSession(session) ++ continue ++ } ++ ++ _, err = stream.Write([]byte{command}) ++ if err != nil { ++ stream.Close() ++ continue ++ } ++ err = M.SocksaddrSerializer.WriteAddrPort(stream, destination) ++ if err != nil { ++ stream.Close() ++ continue ++ } ++ ++ return stream, nil ++ } ++ return nil, E.New("failed to open mux stream") ++} ++ ++func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ++ switch N.NetworkName(network) { ++ case N.NetworkTCP: ++ h.logger.InfoContext(ctx, "outbound connection to ", destination) ++ return h.openStream(ctx, commandTCP, destination) ++ case N.NetworkUDP: ++ h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) ++ conn, err := h.openStream(ctx, commandUDP, uot.RequestDestination(uot.Version)) ++ if err != nil { ++ return nil, err ++ } ++ return uot.NewLazyConn(conn, uot.Request{ ++ IsConnect: true, ++ Destination: destination, ++ }), nil ++ default: ++ return nil, E.New("unsupported network: ", network) ++ } ++} ++ ++func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { ++ h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) ++ conn, err := h.openStream(ctx, commandUDP, uot.RequestDestination(uot.Version)) ++ if err != nil { ++ return nil, err ++ } ++ return uot.NewLazyConn(conn, uot.Request{ ++ IsConnect: false, ++ Destination: destination, ++ }), nil ++} ++ ++func (h *Outbound) InterfaceUpdated() { ++ h.sessionAccess.Lock() ++ defer h.sessionAccess.Unlock() ++ if h.session != nil { ++ _ = common.Close(h.session.session, h.session.conn) ++ h.session = nil ++ } ++} ++ ++func (h *Outbound) Close() error { ++ h.sessionAccess.Lock() ++ defer h.sessionAccess.Unlock() ++ if h.session != nil { ++ err := common.Close(h.session.session, h.session.conn) ++ h.session = nil ++ return err ++ } ++ return nil ++} diff --git a/works/patch/protocol/minecraft/protocol.go.patch b/works/patch/protocol/minecraft/protocol.go.patch new file mode 100644 index 0000000..5878b0e --- /dev/null +++ b/works/patch/protocol/minecraft/protocol.go.patch @@ -0,0 +1,659 @@ +--- /dev/null ++++ b/protocol/minecraft/protocol.go +@@ -0,0 +1,656 @@ ++// OMV ++package minecraft ++ ++import ( ++ "bytes" ++ "crypto/aes" ++ "crypto/cipher" ++ "crypto/rand" ++ "crypto/rsa" ++ "crypto/sha1" ++ "crypto/sha256" ++ "crypto/x509" ++ "encoding/binary" ++ "encoding/json" ++ "fmt" ++ "io" ++ "net" ++ ++ "github.com/sagernet/sing-box/option" ++ "github.com/sagernet/smux" ++) ++ ++// Minecraft protocol constants ++const ( ++ protocolVersion = 765 // 1.20.4 ++ versionName = "1.20.4" ++ maxPacketSize = 2097151 // 2^21 - 1 ++ ++ stateHandshake = 0 ++ stateStatus = 1 ++ stateLogin = 2 ++ ++ // Handshake state packets ++ packetHandshake = 0x00 ++ ++ // Status state packets ++ packetStatusRequest = 0x00 ++ packetStatusResponse = 0x00 ++ packetPingRequest = 0x01 ++ packetPingResponse = 0x01 ++ ++ // Login state packets ++ packetLoginStart = 0x00 ++ packetEncryptionRequest = 0x01 ++ packetEncryptionResponse = 0x01 ++ packetLoginSuccess = 0x02 ++ packetLoginDisconnect = 0x00 ++ ++ rsaKeyBits = 1024 ++ verifyTokenLen = 4 ++ sharedSecretLen = 16 ++) ++ ++// VarInt encoding/decoding ++ ++func readVarInt(r io.Reader) (int32, error) { ++ var result int32 ++ var shift uint ++ buf := make([]byte, 1) ++ for { ++ _, err := io.ReadFull(r, buf) ++ if err != nil { ++ return 0, err ++ } ++ b := buf[0] ++ result |= int32(b&0x7F) << shift ++ if b&0x80 == 0 { ++ break ++ } ++ shift += 7 ++ if shift >= 35 { ++ return 0, fmt.Errorf("VarInt too big") ++ } ++ } ++ return result, nil ++} ++ ++func writeVarInt(w io.Writer, value int32) error { ++ buf := encodeVarInt(value) ++ _, err := w.Write(buf) ++ return err ++} ++ ++func encodeVarInt(value int32) []byte { ++ var buf [5]byte ++ n := 0 ++ uv := uint32(value) ++ for { ++ b := byte(uv & 0x7F) ++ uv >>= 7 ++ if uv != 0 { ++ b |= 0x80 ++ } ++ buf[n] = b ++ n++ ++ if uv == 0 { ++ break ++ } ++ } ++ return buf[:n] ++} ++ ++func varIntLen(value int32) int { ++ return len(encodeVarInt(value)) ++} ++ ++// Packet reading/writing ++ ++func readPacket(r io.Reader) (packetID int32, data []byte, err error) { ++ length, err := readVarInt(r) ++ if err != nil { ++ return 0, nil, err ++ } ++ if length < 0 || length > maxPacketSize { ++ return 0, nil, fmt.Errorf("invalid packet length: %d", length) ++ } ++ payload := make([]byte, length) ++ _, err = io.ReadFull(r, payload) ++ if err != nil { ++ return 0, nil, err ++ } ++ pr := bytes.NewReader(payload) ++ packetID, err = readVarInt(pr) ++ if err != nil { ++ return 0, nil, err ++ } ++ data = payload[varIntLen(packetID):] ++ return packetID, data, nil ++} ++ ++func writePacket(w io.Writer, packetID int32, data []byte) error { ++ idBytes := encodeVarInt(packetID) ++ totalLen := int32(len(idBytes) + len(data)) ++ if err := writeVarInt(w, totalLen); err != nil { ++ return err ++ } ++ if _, err := w.Write(idBytes); err != nil { ++ return err ++ } ++ if _, err := w.Write(data); err != nil { ++ return err ++ } ++ return nil ++} ++ ++// String encoding (VarInt length + UTF-8 bytes) ++ ++func readString(r io.Reader) (string, error) { ++ length, err := readVarInt(r) ++ if err != nil { ++ return "", err ++ } ++ if length < 0 || length > 32767 { ++ return "", fmt.Errorf("string too long: %d", length) ++ } ++ buf := make([]byte, length) ++ _, err = io.ReadFull(r, buf) ++ if err != nil { ++ return "", err ++ } ++ return string(buf), nil ++} ++ ++func encodeString(s string) []byte { ++ var buf bytes.Buffer ++ buf.Write(encodeVarInt(int32(len(s)))) ++ buf.WriteString(s) ++ return buf.Bytes() ++} ++ ++// Byte array encoding (VarInt length + bytes) ++ ++func readByteArray(r io.Reader) ([]byte, error) { ++ length, err := readVarInt(r) ++ if err != nil { ++ return nil, err ++ } ++ if length < 0 || length > 1048576 { ++ return nil, fmt.Errorf("byte array too long: %d", length) ++ } ++ buf := make([]byte, length) ++ _, err = io.ReadFull(r, buf) ++ if err != nil { ++ return nil, err ++ } ++ return buf, nil ++} ++ ++func encodeByteArray(data []byte) []byte { ++ var buf bytes.Buffer ++ buf.Write(encodeVarInt(int32(len(data)))) ++ buf.Write(data) ++ return buf.Bytes() ++} ++ ++// Handshake packet ++ ++type handshakePacket struct { ++ ProtocolVersion int32 ++ ServerAddress string ++ ServerPort uint16 ++ NextState int32 ++} ++ ++func readHandshake(data []byte) (*handshakePacket, error) { ++ r := bytes.NewReader(data) ++ pv, err := readVarInt(r) ++ if err != nil { ++ return nil, err ++ } ++ addr, err := readString(r) ++ if err != nil { ++ return nil, err ++ } ++ var port uint16 ++ if err := binary.Read(r, binary.BigEndian, &port); err != nil { ++ return nil, err ++ } ++ ns, err := readVarInt(r) ++ if err != nil { ++ return nil, err ++ } ++ return &handshakePacket{ ++ ProtocolVersion: pv, ++ ServerAddress: addr, ++ ServerPort: port, ++ NextState: ns, ++ }, nil ++} ++ ++func encodeHandshake(h *handshakePacket) []byte { ++ var buf bytes.Buffer ++ buf.Write(encodeVarInt(h.ProtocolVersion)) ++ buf.Write(encodeString(h.ServerAddress)) ++ binary.Write(&buf, binary.BigEndian, h.ServerPort) ++ buf.Write(encodeVarInt(h.NextState)) ++ return buf.Bytes() ++} ++ ++// Login Start packet ++ ++type loginStartPacket struct { ++ Name string ++ UUID [16]byte ++} ++ ++func readLoginStart(data []byte) (*loginStartPacket, error) { ++ r := bytes.NewReader(data) ++ name, err := readString(r) ++ if err != nil { ++ return nil, err ++ } ++ pkt := &loginStartPacket{Name: name} ++ // Read UUID (16 bytes) if available ++ if r.Len() >= 16 { ++ io.ReadFull(r, pkt.UUID[:]) ++ } ++ return pkt, nil ++} ++ ++func encodeLoginStart(name string) []byte { ++ var buf bytes.Buffer ++ buf.Write(encodeString(name)) ++ // Write zero UUID ++ buf.Write(make([]byte, 16)) ++ return buf.Bytes() ++} ++ ++// Encryption Request packet ++ ++type encryptionRequestPacket struct { ++ ServerID string ++ PublicKey []byte ++ VerifyToken []byte ++} ++ ++func encodeEncryptionRequest(serverID string, pubKey []byte, verifyToken []byte) []byte { ++ var buf bytes.Buffer ++ buf.Write(encodeString(serverID)) ++ buf.Write(encodeByteArray(pubKey)) ++ buf.Write(encodeByteArray(verifyToken)) ++ // Note: ShouldAuthenticate field was added in 1.20.5 (protocol 766). ++ // We target 1.20.4 (protocol 765), so this field is absent. ++ return buf.Bytes() ++} ++ ++func readEncryptionRequest(data []byte) (*encryptionRequestPacket, error) { ++ r := bytes.NewReader(data) ++ serverID, err := readString(r) ++ if err != nil { ++ return nil, err ++ } ++ pubKey, err := readByteArray(r) ++ if err != nil { ++ return nil, err ++ } ++ verifyToken, err := readByteArray(r) ++ if err != nil { ++ return nil, err ++ } ++ return &encryptionRequestPacket{ ++ ServerID: serverID, ++ PublicKey: pubKey, ++ VerifyToken: verifyToken, ++ }, nil ++} ++ ++// Encryption Response packet ++ ++type encryptionResponsePacket struct { ++ SharedSecret []byte ++ VerifyToken []byte ++} ++ ++func encodeEncryptionResponse(sharedSecret []byte, verifyToken []byte) []byte { ++ var buf bytes.Buffer ++ buf.Write(encodeByteArray(sharedSecret)) ++ buf.Write(encodeByteArray(verifyToken)) ++ return buf.Bytes() ++} ++ ++func readEncryptionResponse(data []byte) (*encryptionResponsePacket, error) { ++ r := bytes.NewReader(data) ++ secret, err := readByteArray(r) ++ if err != nil { ++ return nil, err ++ } ++ token, err := readByteArray(r) ++ if err != nil { ++ return nil, err ++ } ++ return &encryptionResponsePacket{ ++ SharedSecret: secret, ++ VerifyToken: token, ++ }, nil ++} ++ ++// Login Success packet ++ ++func encodeLoginSuccess(name string) []byte { ++ // Generate a deterministic UUID from username ++ hash := sha1.Sum([]byte("OfflinePlayer:" + name)) ++ hash[6] = hash[6]&0x0f | 0x30 // version 3 ++ hash[8] = hash[8]&0x3f | 0x80 // variant 2 ++ ++ var buf bytes.Buffer ++ buf.Write(hash[:16]) // UUID ++ buf.Write(encodeString(name)) ++ buf.Write(encodeVarInt(0)) // Number Of Properties = 0 ++ buf.WriteByte(0x01) // Strict Error Handling = true ++ return buf.Bytes() ++} ++ ++// Disconnect (Login) packet — plain text reason ++func encodeLoginDisconnect(reason string) []byte { ++ msg, _ := json.Marshal(map[string]string{"text": reason}) ++ return encodeString(string(msg)) ++} ++ ++// Disconnect (Login) packet — translation key (e.g. multiplayer.disconnect.server_full) ++func encodeLoginDisconnectTranslate(key string) []byte { ++ msg, _ := json.Marshal(map[string]string{"translate": key}) ++ return encodeString(string(msg)) ++} ++ ++// Status Response packet — mirrors real Vanilla server JSON format ++ ++type statusResponse struct { ++ Version *statusVersion `json:"version,omitempty"` ++ Players *statusPlayers `json:"players,omitempty"` ++ Description json.RawMessage `json:"description,omitempty"` ++ Favicon string `json:"favicon,omitempty"` ++ EnforcesSecureChat bool `json:"enforcesSecureChat,omitempty"` ++} ++ ++type statusVersion struct { ++ Name string `json:"name"` ++ Protocol int `json:"protocol"` ++} ++ ++type statusPlayers struct { ++ Max int `json:"max"` ++ Online int `json:"online"` ++ Sample []statusPlayerSample `json:"sample,omitempty"` ++} ++ ++type statusPlayerSample struct { ++ Name string `json:"name"` ++ ID string `json:"id"` ++} ++ ++// encodeDescription converts a json.RawMessage description to the wire format. ++// Accepts a JSON Chat Component (object/array), a JSON string, or null/empty. ++// A JSON string like "hello" is unwrapped and re-wrapped as {"text": "hello"}. ++func encodeDescription(desc json.RawMessage) json.RawMessage { ++ if len(desc) == 0 || string(desc) == "null" { ++ data, _ := json.Marshal(map[string]string{"text": "A Minecraft Server"}) ++ return data ++ } ++ // Already an object or array — use as-is ++ if desc[0] == '{' || desc[0] == '[' { ++ return desc ++ } ++ // JSON string value (e.g. "hello") — unwrap and wrap as {"text": "..."} ++ if desc[0] == '"' { ++ var s string ++ if json.Unmarshal(desc, &s) == nil { ++ data, _ := json.Marshal(map[string]string{"text": s}) ++ return data ++ } ++ } ++ // Fallback ++ data, _ := json.Marshal(map[string]string{"text": "A Minecraft Server"}) ++ return data ++} ++ ++func encodeStatusResponse(status *option.MinecraftStatus) []byte { ++ resp := statusResponse{} ++ ++ // Version ++ if status.Version != nil { ++ resp.Version = &statusVersion{ ++ Name: status.Version.Name, ++ Protocol: status.Version.Protocol, ++ } ++ } else { ++ resp.Version = &statusVersion{Name: versionName, Protocol: protocolVersion} ++ } ++ ++ // Players ++ if status.Players != nil { ++ players := &statusPlayers{ ++ Max: status.Players.Max, ++ Online: status.Players.Online, ++ } ++ for _, s := range status.Players.Sample { ++ id := s.ID ++ if id == "" { ++ hash := sha1.Sum([]byte("OfflinePlayer:" + s.Name)) ++ hash[6] = hash[6]&0x0f | 0x30 ++ hash[8] = hash[8]&0x3f | 0x80 ++ id = fmt.Sprintf("%08x-%04x-%04x-%04x-%012x", hash[0:4], hash[4:6], hash[6:8], hash[8:10], hash[10:16]) ++ } ++ players.Sample = append(players.Sample, statusPlayerSample{Name: s.Name, ID: id}) ++ } ++ resp.Players = players ++ } else { ++ resp.Players = &statusPlayers{Max: 20, Online: 0} ++ } ++ ++ // Description (plain text or JSON Chat Component) ++ resp.Description = encodeDescription(status.Description) ++ ++ // Favicon ++ resp.Favicon = status.Favicon ++ ++ // EnforcesSecureChat ++ resp.EnforcesSecureChat = status.EnforcesSecureChat ++ ++ data, _ := json.Marshal(resp) ++ return encodeString(string(data)) ++} ++ ++// Legacy Server List Ping (pre-1.7) ++// Detects 0xFE as first byte; responds with 0xFF kick containing server info ++ ++const legacyPingByte = 0xFE ++ ++// descriptionToPlainText extracts plain text from a json.RawMessage description. ++// Used for legacy ping which only supports plain text. ++func descriptionToPlainText(desc json.RawMessage) string { ++ if len(desc) == 0 || string(desc) == "null" { ++ return "A Minecraft Server" ++ } ++ // JSON string → unwrap ++ if desc[0] == '"' { ++ var s string ++ if json.Unmarshal(desc, &s) == nil { ++ return s ++ } ++ } ++ // JSON object → extract "text" field ++ if desc[0] == '{' { ++ var obj struct { ++ Text string `json:"text"` ++ } ++ if json.Unmarshal(desc, &obj) == nil && obj.Text != "" { ++ return obj.Text ++ } ++ } ++ return "A Minecraft Server" ++} ++ ++func encodeLegacyPingResponse(status *option.MinecraftStatus) []byte { ++ ver := versionName ++ proto := protocolVersion ++ if status.Version != nil { ++ if status.Version.Name != "" { ++ ver = status.Version.Name ++ } ++ if status.Version.Protocol != 0 { ++ proto = status.Version.Protocol ++ } ++ } ++ motd := descriptionToPlainText(status.Description) ++ var online, max int ++ if status.Players != nil { ++ online = status.Players.Online ++ max = status.Players.Max ++ } else { ++ max = 20 ++ } ++ ++ // Response format: 0xFF + string length (uint16 BE) + UTF-16BE string ++ // String: "§1\0\0\0\0\0" ++ payload := fmt.Sprintf("\u00a71\x00%d\x00%s\x00%s\x00%d\x00%d", ++ proto, ver, motd, online, max) ++ runes := []rune(payload) ++ ++ // Build response: 0xFF + length (uint16 BE) + UTF-16BE chars ++ var buf bytes.Buffer ++ buf.WriteByte(0xFF) ++ binary.Write(&buf, binary.BigEndian, uint16(len(runes))) ++ for _, r := range runes { ++ binary.Write(&buf, binary.BigEndian, uint16(r)) ++ } ++ return buf.Bytes() ++} ++ ++// Shared secret derivation from password + verify token ++ ++func deriveSharedSecret(password string, verifyToken []byte) []byte { ++ h := sha256.New() ++ h.Write([]byte(password)) ++ h.Write(verifyToken) ++ sum := h.Sum(nil) ++ return sum[:sharedSecretLen] ++} ++ ++// AES/CFB8 encrypted connection wrapper ++// Minecraft uses CFB8 mode (1-byte segments), not standard CFB128 ++ ++type cfb8Cipher struct { ++ block cipher.Block ++ iv []byte ++} ++ ++func newCFB8Encrypt(block cipher.Block, iv []byte) *cfb8Cipher { ++ ivCopy := make([]byte, len(iv)) ++ copy(ivCopy, iv) ++ return &cfb8Cipher{block: block, iv: ivCopy} ++} ++ ++func newCFB8Decrypt(block cipher.Block, iv []byte) *cfb8Cipher { ++ ivCopy := make([]byte, len(iv)) ++ copy(ivCopy, iv) ++ return &cfb8Cipher{block: block, iv: ivCopy} ++} ++ ++func (c *cfb8Cipher) encrypt(dst, src []byte) { ++ bs := c.block.BlockSize() ++ tmp := make([]byte, bs) ++ for i := range src { ++ c.block.Encrypt(tmp, c.iv) ++ dst[i] = src[i] ^ tmp[0] ++ copy(c.iv, c.iv[1:]) ++ c.iv[bs-1] = dst[i] ++ } ++} ++ ++func (c *cfb8Cipher) decrypt(dst, src []byte) { ++ bs := c.block.BlockSize() ++ tmp := make([]byte, bs) ++ for i := range src { ++ c.block.Encrypt(tmp, c.iv) ++ copy(c.iv, c.iv[1:]) ++ c.iv[bs-1] = src[i] ++ dst[i] = src[i] ^ tmp[0] ++ } ++} ++ ++// encryptedConn wraps a net.Conn with Minecraft AES/CFB8 encryption ++type encryptedConn struct { ++ net.Conn ++ enc *cfb8Cipher ++ dec *cfb8Cipher ++} ++ ++func newEncryptedConn(conn net.Conn, sharedSecret []byte) (*encryptedConn, error) { ++ block, err := aes.NewCipher(sharedSecret) ++ if err != nil { ++ return nil, err ++ } ++ // Minecraft uses the shared secret as both key and IV ++ return &encryptedConn{ ++ Conn: conn, ++ enc: newCFB8Encrypt(block, sharedSecret), ++ dec: newCFB8Decrypt(block, sharedSecret), ++ }, nil ++} ++ ++func (c *encryptedConn) Read(b []byte) (int, error) { ++ n, err := c.Conn.Read(b) ++ if n > 0 { ++ c.dec.decrypt(b[:n], b[:n]) ++ } ++ return n, err ++} ++ ++func (c *encryptedConn) Write(b []byte) (int, error) { ++ encrypted := make([]byte, len(b)) ++ c.enc.encrypt(encrypted, b) ++ return c.Conn.Write(encrypted) ++} ++ ++// RSA helpers ++ ++func generateRSAKeyPair() (*rsa.PrivateKey, error) { ++ return rsa.GenerateKey(rand.Reader, rsaKeyBits) ++} ++ ++func marshalPublicKey(pub *rsa.PublicKey) ([]byte, error) { ++ return x509.MarshalPKIXPublicKey(pub) ++} ++ ++func parsePublicKey(data []byte) (*rsa.PublicKey, error) { ++ pub, err := x509.ParsePKIXPublicKey(data) ++ if err != nil { ++ return nil, err ++ } ++ rsaPub, ok := pub.(*rsa.PublicKey) ++ if !ok { ++ return nil, fmt.Errorf("not an RSA public key") ++ } ++ return rsaPub, nil ++} ++ ++func generateVerifyToken() ([]byte, error) { ++ token := make([]byte, verifyTokenLen) ++ _, err := rand.Read(token) ++ return token, err ++} ++ ++// smux config ++ ++func smuxConfig() *smux.Config { ++ config := smux.DefaultConfig() ++ config.KeepAliveDisabled = true ++ return config ++} ++ ++// Stream protocol constants (same as MySQL protocol) ++const ( ++ commandTCP byte = 0x01 ++ commandUDP byte = 0x03 ++) diff --git a/works/patch/sing-box.patch b/works/patch/sing-box.patch new file mode 100644 index 0000000..39ceb9f --- /dev/null +++ b/works/patch/sing-box.patch @@ -0,0 +1 @@ +二进制文件 /dev/null 和 b/sing-box 不同 diff --git a/works/patch/test/go.mod.patch b/works/patch/test/go.mod.patch new file mode 100644 index 0000000..9d7a4b0 --- /dev/null +++ b/works/patch/test/go.mod.patch @@ -0,0 +1,205 @@ +--- a/test/go.mod ++++ b/test/go.mod +@@ -10,15 +10,15 @@ + github.com/docker/docker v27.3.1+incompatible + github.com/docker/go-connections v0.5.0 + github.com/gofrs/uuid/v5 v5.4.0 +- github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 +- github.com/sagernet/sing v0.8.0-beta.16 +- github.com/sagernet/sing-quic v0.6.0-beta.11 ++ github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 ++ github.com/sagernet/sing v0.8.1 ++ github.com/sagernet/sing-quic v0.6.0 + github.com/sagernet/sing-shadowsocks v0.2.8 + github.com/sagernet/sing-shadowsocks2 v0.2.1 + github.com/spyzhov/ajson v0.9.4 + github.com/stretchr/testify v1.11.1 + go.uber.org/goleak v1.3.0 +- golang.org/x/net v0.48.0 ++ golang.org/x/net v0.50.0 + ) + + require ( +@@ -28,16 +28,16 @@ + github.com/akutz/memconn v0.1.0 // indirect + github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect + github.com/andybalholm/brotli v1.1.0 // indirect +- github.com/anthropics/anthropic-sdk-go v1.19.0 // indirect ++ github.com/anthropics/anthropic-sdk-go v1.26.0 // indirect + github.com/anytls/sing-anytls v0.0.11 // indirect +- github.com/caddyserver/certmagic v0.25.0 // indirect +- github.com/caddyserver/zerossl v0.1.3 // indirect ++ github.com/caddyserver/certmagic v0.25.2 // indirect ++ github.com/caddyserver/zerossl v0.1.5 // indirect + github.com/coder/websocket v1.8.14 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect + github.com/cretz/bine v0.2.0 // indirect + github.com/database64128/netx-go v0.1.1 // indirect +- github.com/database64128/tfo-go/v2 v2.3.1 // indirect ++ github.com/database64128/tfo-go/v2 v2.3.2 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect + github.com/distribution/reference v0.5.0 // indirect +@@ -48,7 +48,7 @@ + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/gaissmai/bart v0.18.0 // indirect +- github.com/go-chi/chi/v5 v5.2.3 // indirect ++ github.com/go-chi/chi/v5 v5.2.5 // indirect + github.com/go-chi/render v1.0.3 // indirect + github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced // indirect + github.com/go-logr/logr v1.4.3 // indirect +@@ -56,7 +56,7 @@ + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/gobwas/httphead v0.1.0 // indirect + github.com/gobwas/pool v0.2.1 // indirect +- github.com/godbus/dbus/v5 v5.2.1 // indirect ++ github.com/godbus/dbus/v5 v5.2.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/btree v1.1.3 // indirect +@@ -65,26 +65,26 @@ + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect + github.com/hdevalence/ed25519consensus v0.2.0 // indirect +- github.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167 // indirect ++ github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91 // indirect + github.com/jsimonetti/rtnetlink v1.4.0 // indirect + github.com/keybase/go-keychain v0.0.1 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/libdns/acmedns v0.5.0 // indirect +- github.com/libdns/alidns v1.0.6-beta.3 // indirect ++ github.com/libdns/alidns v1.0.6 // indirect + github.com/libdns/cloudflare v0.2.2 // indirect + github.com/libdns/libdns v1.1.1 // indirect + github.com/logrusorgru/aurora v2.0.3+incompatible // indirect +- github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect ++ github.com/mdlayher/netlink v1.9.0 // indirect + github.com/mdlayher/socket v0.5.1 // indirect + github.com/metacubex/utls v1.8.4 // indirect +- github.com/mholt/acmez/v3 v3.1.4 // indirect +- github.com/miekg/dns v1.1.69 // indirect ++ github.com/mholt/acmez/v3 v3.1.6 // indirect ++ github.com/miekg/dns v1.1.72 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect +- github.com/openai/openai-go/v3 v3.15.0 // indirect ++ github.com/openai/openai-go/v3 v3.24.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pierrec/lz4/v4 v4.1.21 // indirect +@@ -96,41 +96,41 @@ + github.com/safchain/ethtool v0.3.0 // indirect + github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect + github.com/sagernet/cors v1.2.1 // indirect +- github.com/sagernet/cronet-go v0.0.0-20260117110918-dc1cda1fe287 // indirect +- github.com/sagernet/cronet-go/all v0.0.0-20260117110918-dc1cda1fe287 // indirect +- github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260117110516-f21660bef13f // indirect +- github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260117110516-f21660bef13f // indirect ++ github.com/sagernet/cronet-go v0.0.0-20260303101018-cba7b9ac0399 // indirect ++ github.com/sagernet/cronet-go/all v0.0.0-20260303101018-cba7b9ac0399 // indirect ++ github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260303100323-125d0d93b3e6 // indirect ++ github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/fswatch v0.1.1 // indirect + github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 // indirect + github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect + github.com/sagernet/nftables v0.3.0-beta.4 // indirect + github.com/sagernet/sing-mux v0.3.4 // indirect + github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 // indirect +- github.com/sagernet/sing-tun v0.8.0-beta.17 // indirect ++ github.com/sagernet/sing-tun v0.8.2-0.20260303130003-70e990468805 // indirect + github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 // indirect + github.com/sagernet/smux v1.5.50-sing-box-mod.1 // indirect +- github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 // indirect +- github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 // indirect ++ github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6.0.20260303140313-3bcf9a4b9349 // indirect ++ github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c // indirect + github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect + github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect + github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect +@@ -149,29 +149,29 @@ + github.com/zeebo/blake3 v0.2.4 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect +- go.opentelemetry.io/otel v1.38.0 // indirect ++ go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect +- go.opentelemetry.io/otel/metric v1.38.0 // indirect +- go.opentelemetry.io/otel/trace v1.38.0 // indirect ++ go.opentelemetry.io/otel/metric v1.39.0 // indirect ++ go.opentelemetry.io/otel/trace v1.39.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.1 // indirect + go.uber.org/zap/exp v0.3.0 // indirect + go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect + go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect +- golang.org/x/crypto v0.46.0 // indirect ++ golang.org/x/crypto v0.48.0 // indirect + golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect +- golang.org/x/mod v0.31.0 // indirect +- golang.org/x/oauth2 v0.32.0 // indirect ++ golang.org/x/mod v0.33.0 // indirect ++ golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect +- golang.org/x/sys v0.39.0 // indirect +- golang.org/x/term v0.38.0 // indirect +- golang.org/x/text v0.32.0 // indirect ++ golang.org/x/sys v0.41.0 // indirect ++ golang.org/x/term v0.40.0 // indirect ++ golang.org/x/text v0.34.0 // indirect + golang.org/x/time v0.11.0 // indirect +- golang.org/x/tools v0.40.0 // indirect ++ golang.org/x/tools v0.42.0 // indirect + golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect + golang.zx2c4.com/wireguard/windows v0.5.3 // indirect +- google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect +- google.golang.org/grpc v1.77.0 // indirect ++ google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect ++ google.golang.org/grpc v1.79.1 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.5.1 // indirect diff --git a/works/patch/test/go.sum.patch b/works/patch/test/go.sum.patch new file mode 100644 index 0000000..ca4e1f4 --- /dev/null +++ b/works/patch/test/go.sum.patch @@ -0,0 +1,281 @@ +--- a/test/go.sum ++++ b/test/go.sum +@@ -14,12 +14,15 @@ + github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= + github.com/anthropics/anthropic-sdk-go v1.19.0 h1:mO6E+ffSzLRvR/YUH9KJC0uGw0uV8GjISIuzem//3KE= + github.com/anthropics/anthropic-sdk-go v1.19.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE= ++github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q= + github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc= + github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= + github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic= + github.com/caddyserver/certmagic v0.25.0/go.mod h1:m9yB7Mud24OQbPHOiipAoyKPn9pKHhpSJxXR1jydBxA= ++github.com/caddyserver/certmagic v0.25.2/go.mod h1:llW/CvsNmza8S6hmsuggsZeiX+uS27dkqY27wDIuBWg= + github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= + github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= ++github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= + github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= + github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= + github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk= +@@ -36,6 +39,7 @@ + github.com/database64128/netx-go v0.1.1/go.mod h1:LNlYVipaYkQArRFDNNJ02VkNV+My9A5XR/IGS7sIBQc= + github.com/database64128/tfo-go/v2 v2.3.1 h1:EGE+ELd5/AQ0X6YBlQ9RgKs8+kciNhgN3d8lRvfEJQw= + github.com/database64128/tfo-go/v2 v2.3.1/go.mod h1:k9wcpg/8i5zenspBkc9jUEYehpZZccBnCElzOJB++bU= ++github.com/database64128/tfo-go/v2 v2.3.2/go.mod h1:GC3uB5oa4beGpCUbRb2ZOWP73bJJFmMyAVgQSO7r724= + github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +@@ -65,6 +69,7 @@ + github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= + github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= + github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= ++github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= + github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= + github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= + github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced h1:Q311OHjMh/u5E2TITc++WlTP5We0xNseRMkHDyvhW7I= +@@ -82,6 +87,7 @@ + github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= + github.com/godbus/dbus/v5 v5.2.1 h1:I4wwMdWSkmI57ewd+elNGwLRf2/dtSaFz1DujfWYvOk= + github.com/godbus/dbus/v5 v5.2.1/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= ++github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= + github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= + github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= + github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +@@ -106,6 +112,7 @@ + github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= + github.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167 h1:MEufgJohwIjFi2n3eJv4c/8UdRLQVUwPwSWQPoER+eU= + github.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167/go.mod h1:qfvBmyDNp+/liLEYWRvqny/PEz9hGe2Dz833eXILSmo= ++github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91/go.mod h1:qfvBmyDNp+/liLEYWRvqny/PEz9hGe2Dz833eXILSmo= + github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= + github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= + github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +@@ -124,6 +131,7 @@ + github.com/libdns/acmedns v0.5.0/go.mod h1:X7UAFP1Ep9NpTwWpVlrZzJLR7epynAy0wrIxSPFgKjQ= + github.com/libdns/alidns v1.0.6-beta.3 h1:KAmb7FQ1tRzKsaAUGa7ZpGKAMRANwg7+1c7tUbSELq8= + github.com/libdns/alidns v1.0.6-beta.3/go.mod h1:RECwyQ88e9VqQVtSrvX76o1ux3gQUKGzMgxICi+u7Ec= ++github.com/libdns/alidns v1.0.6/go.mod h1:RECwyQ88e9VqQVtSrvX76o1ux3gQUKGzMgxICi+u7Ec= + github.com/libdns/cloudflare v0.2.2 h1:XWHv+C1dDcApqazlh08Q6pjytYLgR2a+Y3xrXFu0vsI= + github.com/libdns/cloudflare v0.2.2/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60= + github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= +@@ -132,14 +140,17 @@ + github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= + github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= + github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= ++github.com/mdlayher/netlink v1.9.0/go.mod h1:YBnl5BXsCoRuwBjKKlZ+aYmEoq0r12FDA/3JC+94KDg= + github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= + github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= + github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg= + github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko= + github.com/mholt/acmez/v3 v3.1.4 h1:DyzZe/RnAzT3rpZj/2Ii5xZpiEvvYk3cQEN/RmqxwFQ= + github.com/mholt/acmez/v3 v3.1.4/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= ++github.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY= + github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc= + github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g= ++github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= + github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= + github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= + github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +@@ -152,6 +163,7 @@ + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= + github.com/openai/openai-go/v3 v3.15.0 h1:hk99rM7YPz+M99/5B/zOQcVwFRLLMdprVGx1vaZ8XMo= + github.com/openai/openai-go/v3 v3.15.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= ++github.com/openai/openai-go/v3 v3.24.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= + github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= + github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= + github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +@@ -179,52 +191,76 @@ + github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= + github.com/sagernet/cronet-go v0.0.0-20260117110918-dc1cda1fe287 h1:0BYNmr0ptjsII948U0oBFmrbo4qEaCFcrE2JPRg3Zlk= + github.com/sagernet/cronet-go v0.0.0-20260117110918-dc1cda1fe287/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= ++github.com/sagernet/cronet-go v0.0.0-20260303101018-cba7b9ac0399/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= + github.com/sagernet/cronet-go/all v0.0.0-20260117110918-dc1cda1fe287 h1:ghxhYSBQpzkakqWqJDvXr/Zmxe0WjTjKuALEGbjGiGY= + github.com/sagernet/cronet-go/all v0.0.0-20260117110918-dc1cda1fe287/go.mod h1:M+4ZjPhLJXIvoxcQsbDofmc19Wrig59hZ+hLvj6S3To= ++github.com/sagernet/cronet-go/all v0.0.0-20260303101018-cba7b9ac0399/go.mod h1:MbYagcGGIaRo9tNrgafbCTO+Qc7eVEh32ZWMprSB8b0= + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260117110516-f21660bef13f h1:8jZbZ4KBTdcXDFLwUBNQt5Xci6ZuAKh255S8TwuBCaM= + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260117110516-f21660bef13f/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= ++github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260117110516-f21660bef13f h1:tG0hCx+0u5zca7qQ7AMkcv4DCrBG/DKW1ggs/P+BRRI= + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= ++github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260117110516-f21660bef13f h1:ZXp5hKJIA7iJ52ZShJCKMQEPLpp/7dDIVZmPGV9Il40= + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260117110516-f21660bef13f/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= ++github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260117110516-f21660bef13f h1:gL7H8HS8s38adz4/HZtRHh79qMwsbLTRRPz4GQ9LcWI= + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= ++github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260117110516-f21660bef13f h1:Dchgc0pAY5Jwb5lzUlE+1nhHIzqLx+YOurXLHgvWd/0= + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= ++github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260117110516-f21660bef13f h1:+MOLSQoduuKDxF410i1LcSPaQGaiP0eZb0INvMlmjM4= + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= ++github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260117110516-f21660bef13f h1:lIZna05Vn6n8k21p8OpSUnhwGm+E57PrMjiI4ZUfMSg= + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= ++github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260117110516-f21660bef13f h1:B2aFQ5CRHI20t8YsEizvtguS5W2QfK7D5XV/NzTIxPE= + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= ++github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260117110516-f21660bef13f h1:qpSwJ1rFGYCfJDenNCZoWYjoG7N+xEa6ke+E7/JO1i4= + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= ++github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260117110516-f21660bef13f h1:cx7Ipg0tSvTDjS4maMEYz4vuzz93BMPAysmZ1YLrz80= + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260117110516-f21660bef13f/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= ++github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260117110516-f21660bef13f h1:4jOHuUiBxD8pJEpBBVQfJqyLmxjpd3t4MLRzU7YLFyg= + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= ++github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260117110516-f21660bef13f h1:OpXBa2WlRU+Mam9oRe9Nn4/zf7gQ+qiBTNK8A5RwbfQ= + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= ++github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260117110516-f21660bef13f h1:nJpGFi+6hI85tl4zoyNFEnFEQ5+xEV5gyvsUoMvd8g0= + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= ++github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260117110516-f21660bef13f h1:SEy2rpmgOJgrqcEryJI/RSnqUWIsEsp0cfYoA8y21jc= + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260117110516-f21660bef13f/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= ++github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260117110516-f21660bef13f h1:EW2TuFMLm0iBGqRZtuGwIZdeYmDtDsDmRcRRJQOMxUo= + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= ++github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260117110516-f21660bef13f h1:3U5woxrNCkzfv1+UX+mVoWh1228AE1qAiMG02F9oFbY= + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= ++github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260117110516-f21660bef13f h1:YwFTfuWG3mmctroeDYtFZ6LHjGsedVO+5wInYbbUuUY= + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= ++github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260117110516-f21660bef13f h1:r4V0ddPCRLgGu0VdgR3aUsO9NjpmyjAf+h+3oTD9D6E= + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= ++github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260117110516-f21660bef13f h1:B8yf4gFvEYUnwWmtVK9sdwUsflYZ387MhYmlOP2ohFQ= + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= ++github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260117110516-f21660bef13f h1:9YyaMg4rO1/jIgrxmNb0LKH+X7frSYWfX2pFgW5JUVM= + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= ++github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260117110516-f21660bef13f h1:B0fnGu0sh9yT/9JDN5u/GqThGoOzNN/daOAuGWFLXEk= + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= ++github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260117110516-f21660bef13f h1:lxPcIXKSSI5JDhc7rx/6yufISWM4vtBS2FY9PavWQTs= + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= ++github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= + github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= + github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= + github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 h1:SUPFNB+vSP4RBPrSEgNII+HkfqC8hKMpYLodom4o4EU= +@@ -235,12 +271,15 @@ + github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= + github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 h1:hJUL+HtxEOjxsa0CsucbBVqI/AMS4k52NwNU637zmdw= + github.com/sagernet/quic-go v0.59.0-sing-box-mod.2/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= ++github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= + github.com/sagernet/sing v0.8.0-beta.16 h1:Fe+6E9VHYky9Mx4cf0ugbZPWDcXRflpAu7JQ5bWXvaA= + github.com/sagernet/sing v0.8.0-beta.16/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= ++github.com/sagernet/sing v0.8.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= + github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= + github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= + github.com/sagernet/sing-quic v0.6.0-beta.11 h1:eUusxITKKRedhWC2ScUYFUvD96h/QfbKLaS3N6/7in4= + github.com/sagernet/sing-quic v0.6.0-beta.11/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8= ++github.com/sagernet/sing-quic v0.6.0/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8= + github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= + github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= + github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= +@@ -249,14 +288,17 @@ + github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= + github.com/sagernet/sing-tun v0.8.0-beta.17 h1:6DdbNXeTFYj8Tb4FCh8Mp2boA3rVY6VNqzTOObj7Xis= + github.com/sagernet/sing-tun v0.8.0-beta.17/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= ++github.com/sagernet/sing-tun v0.8.2-0.20260303130003-70e990468805/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs= + github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= + github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= + github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= + github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8= + github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 h1:eYz/OpMqWCvO2++iw3dEuzrlfC2xv78GdlGvprIM6O8= + github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6/go.mod h1:m87GAn4UcesHQF3leaPFEINZETO5za1LGn1GJdNDgNc= ++github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6.0.20260303140313-3bcf9a4b9349/go.mod h1:m87GAn4UcesHQF3leaPFEINZETO5za1LGn1GJdNDgNc= + github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 h1:E2tZFeg9mGYGQ7E7BbxMv1cU35HxwgRm6tPKI2Pp7DA= + github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0= ++github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0= + github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= + github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= + github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +@@ -314,18 +356,21 @@ + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= + go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= + go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= ++go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU= + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4= + go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= + go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= ++go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= + go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= + go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= + go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= + go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= + go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= + go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= ++go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= + go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= + go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= + go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +@@ -346,6 +391,7 @@ + golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= + golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= + golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= ++golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= + golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0= + golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= + golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= +@@ -354,6 +400,7 @@ + golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= + golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= + golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= ++golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= + 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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +@@ -362,8 +409,10 @@ + golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= + golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= + golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= ++golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= + golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= + golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= ++golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= + golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= + golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= + golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +@@ -381,14 +430,17 @@ + golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= + golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= + golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= ++golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= + golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= + golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= + golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= ++golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= + golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= + golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= + golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= + golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= + golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= ++golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= + golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= + golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= + golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +@@ -397,6 +449,7 @@ + golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= + golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= + golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= ++golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= + 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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +@@ -411,8 +464,10 @@ + google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= + google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= + google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= ++google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= + google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= + google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= ++google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= + google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= + google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= + gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=