add minecraft protocol
This commit is contained in:
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,25 @@
|
|||||||
--- a/constant/proxy.go
|
--- a/constant/proxy.go
|
||||||
+++ b/constant/proxy.go
|
+++ b/constant/proxy.go
|
||||||
@@ -31,6 +31,7 @@
|
@@ -31,6 +31,8 @@
|
||||||
TypeCCM = "ccm"
|
TypeCCM = "ccm"
|
||||||
TypeOCM = "ocm"
|
TypeOCM = "ocm"
|
||||||
TypeOOMKiller = "oom-killer"
|
TypeOOMKiller = "oom-killer"
|
||||||
+ TypeMySQL = "mysql" // OMV
|
+ TypeMySQL = "mysql" // OMV
|
||||||
|
+ TypeMinecraft = "minecraft" // OMV
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -88,6 +89,10 @@
|
@@ -88,6 +90,14 @@
|
||||||
return "AnyTLS"
|
return "AnyTLS"
|
||||||
case TypeTailscale:
|
case TypeTailscale:
|
||||||
return "Tailscale"
|
return "Tailscale"
|
||||||
+ // OMV start: register mysql type name
|
+ // OMV start: register mysql type name
|
||||||
+ case TypeMySQL:
|
+ case TypeMySQL:
|
||||||
+ return "MySQL"
|
+ return "MySQL"
|
||||||
|
+ // OMV end
|
||||||
|
+ // OMV start: register minecraft type name
|
||||||
|
+ case TypeMinecraft:
|
||||||
|
+ return "Minecraft"
|
||||||
+ // OMV end
|
+ // OMV end
|
||||||
case TypeSelector:
|
case TypeSelector:
|
||||||
return "Selector"
|
return "Selector"
|
||||||
|
|||||||
@@ -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,<data>",
|
||||||
|
+ "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,<data>` 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.
|
||||||
@@ -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,<data>",
|
||||||
|
+ "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,<data>`,显示在服务器列表中。
|
||||||
|
+
|
||||||
|
+##### 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`),并返回格式正确的响应。
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
--- /dev/null
|
--- /dev/null
|
||||||
+++ b/docs/configuration/omv.zh.md
|
+++ b/docs/configuration/omv.zh.md
|
||||||
@@ -0,0 +1,57 @@
|
@@ -0,0 +1,55 @@
|
||||||
+// OMV
|
|
||||||
+
|
|
||||||
+# OMV 改动说明
|
+# OMV 改动说明
|
||||||
+
|
+
|
||||||
+本页面记录了 OMV(omv-dijiang)在上游 sing-box 基础上所做的全部改动。
|
+本页面记录了 OMV(omv-dijiang)在上游 sing-box 基础上所做的全部改动。
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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 地址头
|
||||||
@@ -1,26 +1,29 @@
|
|||||||
--- a/include/registry.go
|
--- a/include/registry.go
|
||||||
+++ b/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/group"
|
||||||
"github.com/sagernet/sing-box/protocol/http"
|
"github.com/sagernet/sing-box/protocol/http"
|
||||||
"github.com/sagernet/sing-box/protocol/mixed"
|
"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/mysql" // OMV
|
||||||
"github.com/sagernet/sing-box/protocol/naive"
|
"github.com/sagernet/sing-box/protocol/naive"
|
||||||
"github.com/sagernet/sing-box/protocol/redirect"
|
"github.com/sagernet/sing-box/protocol/redirect"
|
||||||
"github.com/sagernet/sing-box/protocol/shadowsocks"
|
"github.com/sagernet/sing-box/protocol/shadowsocks"
|
||||||
@@ -62,6 +63,7 @@
|
@@ -62,6 +64,8 @@
|
||||||
shadowtls.RegisterInbound(registry)
|
shadowtls.RegisterInbound(registry)
|
||||||
vless.RegisterInbound(registry)
|
vless.RegisterInbound(registry)
|
||||||
anytls.RegisterInbound(registry)
|
anytls.RegisterInbound(registry)
|
||||||
+ mysql.RegisterInbound(registry) // OMV
|
+ mysql.RegisterInbound(registry) // OMV
|
||||||
|
+ minecraft.RegisterInbound(registry) // OMV
|
||||||
|
|
||||||
registerQUICInbounds(registry)
|
registerQUICInbounds(registry)
|
||||||
registerStubForRemovedInbounds(registry)
|
registerStubForRemovedInbounds(registry)
|
||||||
@@ -90,6 +92,7 @@
|
@@ -90,6 +94,8 @@
|
||||||
shadowtls.RegisterOutbound(registry)
|
shadowtls.RegisterOutbound(registry)
|
||||||
vless.RegisterOutbound(registry)
|
vless.RegisterOutbound(registry)
|
||||||
anytls.RegisterOutbound(registry)
|
anytls.RegisterOutbound(registry)
|
||||||
+ mysql.RegisterOutbound(registry) // OMV
|
+ mysql.RegisterOutbound(registry) // OMV
|
||||||
|
+ minecraft.RegisterOutbound(registry) // OMV
|
||||||
|
|
||||||
registerQUICOutbounds(registry)
|
registerQUICOutbounds(registry)
|
||||||
registerStubForRemovedOutbounds(registry)
|
registerStubForRemovedOutbounds(registry)
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
+}
|
||||||
@@ -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)
|
||||||
|
+}
|
||||||
@@ -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
|
||||||
|
+}
|
||||||
@@ -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<protocol>\0<version>\0<motd>\0<online>\0<max>"
|
||||||
|
+ 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
|
||||||
|
+)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
二进制文件 /dev/null 和 b/sing-box 不同
|
||||||
@@ -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
|
||||||
@@ -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=
|
||||||
Reference in New Issue
Block a user