Skip to content

Comments

Finalmask: Add XDNS (relies on mKCP, like DNSTT), header-*, mkcp-*#5560

Merged
RPRX merged 5 commits intoXTLS:mainfrom
LjhAUMEM:kcp
Jan 31, 2026
Merged

Finalmask: Add XDNS (relies on mKCP, like DNSTT), header-*, mkcp-*#5560
RPRX merged 5 commits intoXTLS:mainfrom
LjhAUMEM:kcp

Conversation

@LjhAUMEM
Copy link
Contributor

@LjhAUMEM LjhAUMEM commented Jan 18, 2026

将 kcp header/security 移到 finalmask/udp,新版要连接旧版 kcp 未配置 seed 要带个 mkcp-original mask

添加 xdns,参考
e111260
dnstt
目前只能搭配 kcp + 配置 mtu,且需要在最外层
一般来说客户端 mtu 130,服务端 mtu 900,极限值也多不了多少,域名过长客户端 mtu 需要减一点,加额外 mask 也要减一点

各个 mask 会额外占用的字节

名称 字节
mkcp-aes128gcm 28
mkcp-original 6
header-dns 取决于域名
header-dtls 13
header-srtp 4
header-utp 4
header-wechat 13
header-wireguard 4
salamander 8

简易示例配置

{
  "log": { "loglevel": "debug" },
  "inbounds": [
    {
      "listen": "127.0.0.1",
      "port": 1080,
      "protocol": "socks",
      "settings": {
        "auth": "noauth",
        "udp": true
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "vless",
      "settings": {
        "address": "127.0.0.1",
        "port": 1081,
        "id": "5783a3e7-e373-51cd-8642-c83782b807c5",
        "encryption": "none"
      },
      "streamSettings": {
        "network": "kcp",
        "finalmask": {
          "udp": [
            {
              "type": "header-dtls"
            },
            {
              "type": "mkcp-original"
            },
            {
              "type": "mkcp-aes128gcm",
              "settings": {
                "password": "123"
              }
            }
          ]
        }
      }
    }
  ]
}
{
  "log": { "loglevel": "debug" },
  "inbounds": [
    {
      "listen": "127.0.0.1",
      "port": 1081,
      "protocol": "vless",
      "settings": {
        "clients": [
          {
            "id": "5783a3e7-e373-51cd-8642-c83782b807c5"
          }
        ],
        "decryption": "none"
      },
      "streamSettings": {
        "network": "kcp",
        "finalmask": {
          "udp": [
            {
              "type": "header-dtls"
            },
            {
              "type": "mkcp-original"
            },
            {
              "type": "mkcp-aes128gcm",
              "settings": {
                "password": "123"
              }
            }
          ]
        }
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "freedom"
    }
  ]
}

@LjhAUMEM
Copy link
Contributor Author

先放几天吧我自己也要 review 一下

@Fangliding
Copy link
Member

让kcp能用 salamander 就差不多得了吧 那堆伪装就是兼容选项 这一break反而让它们没有意义了 让AI把它挪到udp mask就是把旧史搬上去
而且它只伪装头 从这个角度还不如那堆udp noise

@LjhAUMEM
Copy link
Contributor Author

让AI把它挪到

并不是 AI,我移了几天

它只伪装头

这个头确实有问题,服务端回包格式和客户端发包格式是一样的,也许以后根据 rfc 改下就有资格上桌了

从这个角度还不如那堆udp noise

noise 要延迟发包和只加头或者异或/加密混淆不同,还不打算看这个,如果不需要 kcp mask 我就先去看 hy transport hub 了

@Fangliding
Copy link
Member

那还是先弄hy入站吧 这个有用点 不然复现问题还得让人装个singbox非常难绷

@RPRX
Copy link
Member

RPRX commented Jan 18, 2026

正想把分享链接中那个 mKCP seed 删掉给 VLESS Seed 让位,所以 udpmasks 里别再叫 seed 了改名 psk 之类的吧

@RPRX
Copy link
Member

RPRX commented Jan 18, 2026

如果不是真正的前面就加个 fake- 与以后要加的真正的区别开吧也不用放 header 文件夹里了,另外 rebase 一下,test 修一下

@RPRX
Copy link
Member

RPRX commented Jan 18, 2026

@Fangliding 现在有了 hy2 要不把 kcp 删了吧,没几个人用就是说,现在想暴力发包的都去用 hy2 了

如果想用 VLESS 的 reverse 等特性,VLESS 也能接上 hy2 传输层,等 Xray 有了 hy2 inbound 的时候

@Fangliding
Copy link
Member

别 有人用的

@RPRX
Copy link
Member

RPRX commented Jan 18, 2026

他们用的逻辑不就是 kcp 还有那些伪装吗,那些伪装会被迁移到 udpmasks 然后催 UI 更新一下就能完全取代 kcp 了

@RPRX
Copy link
Member

RPRX commented Jan 18, 2026

我是觉得 UDP 可靠传输/暴力发包这块比起来土制协议还是全部基于 QUIC 更好,要什么外观把伪装叠上去就行

@Fangliding
Copy link
Member

我觉得一个更精简的UDP协议还是有必要的 quic太重了 自己想改点都无从下手

@RPRX
Copy link
Member

RPRX commented Jan 18, 2026

也是那就先留着吧,不过等 udpmasks 弄差不多了、Xray 支持 hy2 服务端了,估计用 kcp 更没人用了,机场甚至推出 Xray 版 hy2

@RPRX
Copy link
Member

RPRX commented Jan 18, 2026

@LjhAUMEM 话说这 QUIC 库方便改 UDP 包最大长度不,不然 udpmasks 叠两层就炸了

@RPRX
Copy link
Member

RPRX commented Jan 18, 2026

@LjhAUMEM 还有 udpmasks 层能实现个统一的预留长度不,不然对于先弄成全随机数再叠上 fake header 来说性能上太吃亏了

@LjhAUMEM
Copy link
Contributor Author

@LjhAUMEM 话说这 QUIC 库方便改 UDP 包最大长度不,不然 udpmasks 叠两层就炸了

好像不受 quic 包长度限制,因为到服务端也是先经过 pktconn 解包了才会到 quic,长度恢复正常,只是大包在公网应该更容易丢包

@LjhAUMEM 还有 udpmasks 层能实现个统一的预留长度不,不然对于先弄成全随机数再叠上 fake header 来说性能上太吃亏了

每个连接搞个全局缓冲区吗,感觉没必要啊,损失点就损失点吧

@RPRX
Copy link
Member

RPRX commented Jan 19, 2026

就是因为大包在公网更容易丢包所以才得限制原始 UDP 包最大长度以便叠上多层 udpmasks 后不容易丢包

无优化的“先弄成全随机数再叠上 fake header”直接多了一次没必要的 copy,所以肯定先要给后面的 udpmask 留一些头部空间

@LjhAUMEM
Copy link
Contributor Author

就是因为大包在公网更容易丢包所以才得缩短原始 UDP 包长度以便叠上多层 udpmasks 后不会丢包

看了下只能设置 datagram 的包长度大小,对于 steam 的只能设置缓冲区大小,再深入就得去魔改 quic-go 了

无优化的“先弄成全随机数再叠上 fake header”直接多了一次没必要的 copy,所以肯定先要给后面的 udpmask 留一些头部空间

应该可以在第一次 writeto 获取后面 header 的 size 和加密的 overhead 一起申请了,我看看,但是如果后面是个 salamander 可能还是多一次 copy,salamander 自己搞了个缓冲区

@RPRX
Copy link
Member

RPRX commented Jan 19, 2026

XOR 的本来就要多 copy 一次倒无所谓,只是有没有复用原内存的问题,不过 AEAD 的话

@RPRX
Copy link
Member

RPRX commented Jan 19, 2026

不对,AEAD 的复用不了内存只是针对一整条信息分多个块,而对于分包发送,有尾部空间的话还是可以复用内存的

@LjhAUMEM
Copy link
Contributor Author

LjhAUMEM commented Jan 19, 2026

重构了一下 salamander 改为 buf 的实现,删掉了独有的缓冲区

现在每个 udpmask 都支持作为第一个 writeto 时为后面的写入预留空间,我自己都绕进去了,还需要再看下

aead 加解密后的长度是会变化的,好消息是不会影响预留出来的空间,但是会延长后面的空间,如果作为 first 时申请的空间不够多可能会出问题

@LjhAUMEM
Copy link
Contributor Author

其实 salamander 搞这个缓冲区还是不错的,不然并发写入都 stacknew 一下有点难崩,可以只在 first 维护一个 8192 的缓冲区,后面的都共享这块读写

@LjhAUMEM
Copy link
Contributor Author

看了下 aead 加密后的长度其实是固定的,为 overhead+len(plaintext),然后 nonce 也要传过去再加个 nonceSize,我写了个 test 可以跑一下,顺便把不太规范的 simple open 改了

udpmask 应该可以了,剩下的就是上配置文件测试,如果测试没问题再看下 kcp 和删掉的部分应该没了

@LjhAUMEM
Copy link
Contributor Author

除去已测试的与旧客户端 dtls+aes-gcm-128 psk 可连接成功,下面的配置也测试成功

客户端

{
  "log": { "loglevel": "debug" },
  "inbounds": [
    {
      "listen": "127.0.0.1",
      "port": 1080,
      "protocol": "socks",
      "settings": {
        "auth": "noauth",
        "udp": true
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "vless",
      "settings": {
        "address": "127.0.0.1",
        "port": 1081,
        "id": "5783a3e7-e373-51cd-8642-c83782b807c5",
        "encryption": "none"
      },
      "streamSettings": {
        "network": "kcp",
        "kcpSettings": {
        },
        "udpmasks": [
          {
            "type": "wechat"
          },
          {
            "type": "dtls"
          },
          {
            "type": "salamander",
            "settings": {
              "password": "0123"
            }
          },
          {
            "type": "salamander",
            "settings": {
              "password": "3210"
            }
          },
          {
            "type": "aesgcm128",
            "settings": {
              "psk": "123"
            }
          },
          {
            "type": "aesgcm128",
            "settings": {
              "psk": "321"
            }
          },
          {
            "type": "simple"
          },
          {
            "type": "simple"
          }
        ]
      }
    }
  ]
}

服务端

{
  "log": { "loglevel": "debug" },
  "inbounds": [
    {
      "listen": "127.0.0.1",
      "port": 1081,
      "protocol": "vless",
      "settings": {
        "clients": [
          {
            "id": "5783a3e7-e373-51cd-8642-c83782b807c5"
          }
        ],
        "decryption": "none"
      },
      "streamSettings": {
        "network": "kcp",
        "kcpSettings": {
        },
        "udpmasks": [
          {
            "type": "wechat"
          },
          {
            "type": "dtls"
          },
          {
            "type": "salamander",
            "settings": {
              "password": "0123"
            }
          },
          {
            "type": "salamander",
            "settings": {
              "password": "3210"
            }
          },
          {
            "type": "aesgcm128",
            "settings": {
              "psk": "123"
            }
          },
          {
            "type": "aesgcm128",
            "settings": {
              "psk": "321"
            }
          },
          {
            "type": "simple"
          },
          {
            "type": "simple"
          }
        ]
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "freedom"
    }
  ]
}

以及各个 mask 会额外占用的字节

类型 名称 字节
crypt aes-gcm-128 28
crypt simple 6
header dns 取决于域名
header dtls 13
header srtp 4
header utp 4
header wechat 13
header wireguard 4
xor salamander 8

也就是说在 quic stream 单包总是 < 1500 的情况下,8192 的大小套几百层不是问题

@RPRX
Copy link
Member

RPRX commented Jan 20, 2026

DNSTT 那个好实现吗

@LjhAUMEM
Copy link
Contributor Author

DNSTT 那个好实现吗

还没看过,但如果只是编码和解包应该不难 看了下这个实现是把客户端上行放在 Question.Name 里,限制 255,可能还要实现分片重组逻辑

还发现一篇不错的博文 https://www.bamsoftware.com/software/dnstt/

@LjhAUMEM
Copy link
Contributor Author

已测试 dlts+simple/aesgcm128 psk 与旧版连接无问题,一层 salamander hy 服务端也正常

我想了想既然可以加 first 同理也可以加 end/last 让 mask 知道自己是不是最外层,以后加入 icmp 这种只能放在最外层的伪装也方便

dnstt 要加的话建议放在传输层而不是伪装层,因为要集成 kcp 的 mtu 来实现分片上传,而且放传输层以后如果要支持 DOT DOH 也方便

@RPRX
Copy link
Member

RPRX commented Jan 24, 2026

如果要把“基于 DNS 的隧道”放到传输层那岂不是又要实现一套可靠传输机制?直接用 QUIC 就行才想着把 DNSTT 这种放伪装层

@RPRX
Copy link
Member

RPRX commented Jan 24, 2026

就是说要把“可靠传输”和“单纯伪装”这两件事解耦,不然 ICMP、DNS、IP/TCP header 啥的全是不可靠的,都要自己实现可靠传输,开发和维护成本太高了

@Fangliding
Copy link
Member

这跟salamander不一样 它把数据包混淆了 发过去 服务端解混淆一样 完全透明 完全无状态 啥也不用管
要用dns发消息 那就跟meek一样 要维护一个query/answer的轮询 这是一个状态机了 不是简单给数据包加壳脱壳

@RPRX
Copy link
Member

RPRX commented Jan 24, 2026

目前想法就是 XHTTP/3 over XOR over ICMP/DNS/IP/TCP header 啥的,目前还需要 XHTTP/3 那一层限制一下最大 QUIC 包

@RPRX
Copy link
Member

RPRX commented Jan 31, 2026

@LjhAUMEM 人呢

Salamander 和 XDNS 内为啥又搞了一个文件夹,可以去掉吗

@LjhAUMEM
Copy link
Contributor Author

@LjhAUMEM 人呢

奇怪,这次 rebase 全是冲突,70个提交要一个一个解决

Salamander 和 XDNS 内为啥又搞了一个文件夹,可以去掉吗

finalmask/udp/salamander finalmask/udp/xdns 这样不对吗

@RPRX
Copy link
Member

RPRX commented Jan 31, 2026

因为我 force-push 了 main

我的意思是代码里 Salamander 和 XDNS 内为啥需要多套个文件夹

@RPRX
Copy link
Member

RPRX commented Jan 31, 2026

finalmask/udp/salamander finalmask/udp/xdns 这样不对吗

不对,把 udp 那一层文件夹去掉,以后有可能一个名称同时支持 UDP 和 TCP

@LjhAUMEM
Copy link
Contributor Author

不对,把 udp 那一层文件夹去掉,以后有可能一个名称同时支持 UDP 和 TCP

完成

我的意思是代码里 Salamander 和 XDNS 内为啥需要多套个文件夹

那些是原来仓库里的结构,想着破坏少点就保留了

@LjhAUMEM
Copy link
Contributor Author

为什么这次 rebase 总感觉不对劲,不确定是不是有些文件被改了

@RPRX
Copy link
Member

RPRX commented Jan 31, 2026

都是你写的代码,别搞那么多文件夹嵌套吧

你这 rebase 我也感觉怪怪的,你能先把所有 commit 压成一个吗

@RPRX
Copy link
Member

RPRX commented Jan 31, 2026

@LjhAUMEM
Copy link
Contributor Author

都是你写的代码,别搞那么多文件夹嵌套吧

那我改一下

你这 rebase 我也感觉怪怪的,你能先把所有 commit 压成一个吗

压到一个了,我重新 review 一下全部

@RPRX
Copy link
Member

RPRX commented Jan 31, 2026

Core 的 version 还是被改了

Salamander 和 XDNS 的文件夹还是嵌套了

@LjhAUMEM
Copy link
Contributor Author

XDNS 那个留着吧,就一堆结构体,和 conn 放一起不太合适

@RPRX
Copy link
Member

RPRX commented Jan 31, 2026

放一起没事的,改一下,不要搞太多文件夹嵌套

mkcp 那个 removed 提示,udpmasks 字样改成 finalmask/udp

@LjhAUMEM
Copy link
Contributor Author

应该可以了,看了一下最重要的 mask conn 部分没问题,kcp 也粗略看了一下,应该就是原来写的样子

icmp 不太好调试,需要起码两个设备,可能要推迟一下,不过也是这几天

@RPRX
Copy link
Member

RPRX commented Jan 31, 2026

更新一下 PR 正文描述

@RPRX RPRX changed the title kcp masks Finalmask: Add XDNS (relies mKCP, like DNSTT), header-*, mkcp-* Jan 31, 2026
@RPRX RPRX merged commit c180c59 into XTLS:main Jan 31, 2026
39 checks passed
@RPRX RPRX changed the title Finalmask: Add XDNS (relies mKCP, like DNSTT), header-*, mkcp-* Finalmask: Add XDNS (relies on mKCP, like DNSTT), header-*, mkcp-* Feb 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants