Thanks to visit codestin.com
Credit goes to github.com

Skip to content

header custom & fix dialerProxy with finalmask/udp#5657

Open
LjhAUMEM wants to merge 70 commits intoXTLS:mainfrom
LjhAUMEM:header-custom
Open

header custom & fix dialerProxy with finalmask/udp#5657
LjhAUMEM wants to merge 70 commits intoXTLS:mainfrom
LjhAUMEM:header-custom

Conversation

@LjhAUMEM
Copy link
Contributor

@LjhAUMEM LjhAUMEM commented Feb 5, 2026

  1. tcp header-custom

tcp 包单元
delay: Int32Range 单位毫秒,为 0 会自动与前面合包
rand: int32 随机长度
type: packet type (array, str, hex, base64)
packet: []byte

tcp 对话序列: tcp 包单元 array

tcp header-custom settings
clients: tcp 对话序列 array,与 servers 遵循一发一收
servers: tcp 对话序列 array,与 clients 遵循一收一发
errors: tcp 对话序列 array,仅服务端,对于 clients 发来的验证不通过触发,无 clients 则不会触发

  1. udp header-custom

udp 包单元
rand: int32 随机长度
type: packet type (array, str, hex, base64)
packet: []byte

udp header-custom settings
client: udp 包单元 array,总是合并后加入每个 udp 包头,对于 rand 包单元则只验证长度
server: udp 包单元 array,总是合并后加入每个 udp 包头,对于 rand 包单元则只验证长度

  1. fragment

from freedom

只修改配置的 interval 为 delay,其他与原来不变

  1. noise

from freedom

udp 包单元
rand: Int32Range 随机长度
type: packet type (array, str, hex, base64)
packet: []byte
delay: Int32Range 单位毫秒,发送一个 item 后延迟多少

noise settings
reset Int32Range 单位秒,对于同个连接周期已经发送过的 addr 进行 reset 后重新发送,默认 0 为全局同个 addr 只发送一次
noise: udp 包单元 array

  1. 其他修改

修复 finalmask/udp dialer proxy
重构 finalmask/udp
xhttp h3 套上 finalmask/udp

  1. 三个 tcp header-custom 示例

伪装 ssh banner,对应 SSH-2.0-OpenSSH_10.0p2 Debian-7\r\n

"finalmask": {
  "tcp": [
    {
      "type": "header-custom",
      "settings": {
        "clients": [],
        "servers": [
          [{"type": "str", "packet": "SSH-2.0-OpenSSH_10.0p2 Debian-7\r\n"}]
        ]
      }
    }
  ]
}

伪装 socks5 user pass 代理 example.com

"finalmask": {
  "tcp": [
    {
      "type": "header-custom",
      "settings": {
        "clients": [
          [{"type": "hex", "packet": "050102"}],
          [{"type": "hex", "packet": "0104757365720470617373"}],
          [{"type": "hex", "packet": "050100030b6578616d706c652e636f6d01bb"}]
        ],
        "servers": [
          [{"type": "hex", "packet": "0502"}],
          [{"type": "hex", "packet": "0100"}],
          [{"type": "hex", "packet": "05000001000000000000"}]
        ]
      }
    }
  ]
}

对应 hex

05 01 02

05 02

01 04 75 73 65 72 04 70 61 73 73

01 00

05 01 00 03 0b 65 78 61 6d 70 6c 65 2e 63 6f 6d 01 bb

05 00 00 01 00 00 00 00 00 00

伪装 smtp 587 starttls,一般搭配 raw + tls/reality

其他实现 tls 内层应该还会进行一次 smtp 验证,所以此 header 不防主动探测,但骗下 DPI 还是没问题

"finalmask": {
  "tcp": [
    {
      "type": "header-custom",
      "settings": {
        "clients": [
          [],
          [{"type": "str", "packet": "EHLO client\r\n"}], // [{"type": "str", "packet": "EHLO"}, {"rand": 7}, {"type": "str", "packet": "\r\n"}]
          [{"type": "str", "packet": "STARTTLS\r\n"}]
        ],
        "servers": [
          [{"type": "str", "packet": "220 foo.com ESMTP Postfix (Ubuntu)\r\n"}],
          [{"type": "str", "packet": "250-foo.com\r\n"}, {"type": "str", "packet": "250-STARTTLS\r\n"}, {"type": "str", "packet": "250 AUTH PLAIN LOGIN\r\n"}],
          [{"type": "str", "packet": "220 Ready to start TLS\r\n"}]
        ],
        "errors": [
          [],
          [{"type": "str", "packet": "503 5.5.1 Bad sequence of commands\r\n"}],
          [{"type": "str", "packet": "503 5.5.1 Bad sequence of commands\r\n"}]
        ]
      }
    }
  ]
}

@LjhAUMEM
Copy link
Contributor Author

LjhAUMEM commented Feb 5, 2026

还没测试

@RPRX
Copy link
Member

RPRX commented Feb 5, 2026

盲猜 Splice 啥的会炸,新版先不包含这个吧,这俩又不急,设计还可以再讨论下

@LjhAUMEM
Copy link
Contributor Author

LjhAUMEM commented Feb 6, 2026

盲猜 Splice 啥的会炸

tcp 那个吗,确实有点问题,流式的可能多次 read 才收到对端完整的一次 write

关于 Splice 那就给 tcpmask 多加个解包接口可以给 UnwrapRawConn 使用应该可以

感觉修复 dialerProxy 那个应该没啥问题,是不是应该放另一个 pr

@MoRanYue
Copy link

MoRanYue commented Feb 6, 2026

header-*mkcp-*先前仅支持mKCP,改为“伪装层”后,是否能够应用于WIreGuard、Hysteria2呢?)

@LjhAUMEM
Copy link
Contributor Author

LjhAUMEM commented Feb 6, 2026

header-*mkcp-*先前仅支持mKCP,改为“伪装层”后,是否能够应用于WIreGuard、Hysteria2呢?)

已经可以了,只是无法搭配 dialerProxy,这个 pr 应该修复了

@RPRX
Copy link
Member

RPRX commented Feb 6, 2026

Finalmask 加 dialer-proxy 的话有点用但不多,不过 header-custom 可玩性较高,我想想

@RPRX
Copy link
Member

RPRX commented Feb 6, 2026

先不包含吧,不然搞出问题的话又要发版

@LjhAUMEM
Copy link
Contributor Author

LjhAUMEM commented Feb 6, 2026

先不包含吧,不然搞出问题的话又要发版

嗯,我也只测试了 dialerProxy 部分,那两个 header 还没有,tryunwrappermask 也没加

@LjhAUMEM
Copy link
Contributor Author

LjhAUMEM commented Feb 6, 2026

看了下 Splice 对于出站只需要读方向的 raw conn,对于入站只需要写方向的 raw conn,而 header-custom 和 fragment 都满足这一要求,所以可以直接默认 tcpmask 支持解 wrap

简单测试了下 tcp header-custom 以及有无 tcpmask 的 splice 下的行为和之前一样

明天再测下 udp 的 header-custom 以及 conn 清理细节方面应该就可以了 还要顺便说明下 tcp header,其实可以 clients 不填只填 servers 伪装个 ssh banner

睡觉

@LjhAUMEM
Copy link
Contributor Author

LjhAUMEM commented Feb 9, 2026

改了下,去掉 OnCloseHeaderError,增加 ServersError,可选在不同 clients 位置过来的数据不对时回应的不同错误

@RPRX
Copy link
Member

RPRX commented Feb 12, 2026

没记错的话 Splice 是要两边都直接 fd 吧,可能你测错了

header-custom 的话直接照抄 noises 吧有 str 方便些,如果 rand 就只读取长度、不验证内容

顺便把 fragment 和 noises break 过来吧,趁着伊朗也不太能用它们,noises 改名 noise 因为 fm 自带数组,顺便推动 GUI 加 fm

这下 fragment 也能 Splice 了,fragment、noises 也支持分享了

@RPRX
Copy link
Member

RPRX commented Feb 12, 2026

Wait 不太需要单独 noise,就放 header-custom 的 UDP 里,可以加个选项 times,1 的话就是只发一次,带 delay 的话就是单独发

@RPRX
Copy link
Member

RPRX commented Feb 12, 2026

header-custom 的 TCP 同理也要加 delay 这个选项,不 delay 的话要粘包发送,都先等有数据吧确保 delay 的准确性

值得注意的是 header-custom UDP 有 delay 时两端可以不同,可能就出站/入站先发几个包给 GFW 演一下,有点烧脑

@RPRX
Copy link
Member

RPRX commented Feb 12, 2026

对于 UDP 就默认它可能会丢包然后有 delay 的就是不验证对端包吧,比如入站写了 client 但有 delay,只发自己 server 的就行

@LjhAUMEM
Copy link
Contributor Author

没记错的话 Splice 是要两边都直接 fd 吧,可能你测错了

CopyRawConnIfExist 只在 freedom 出站和 xtls 里用到,freedom 里在 responseDone,获取 inbound 的 conn 用 tc.ReadFrom 如果有,ReadFrom 也只是影响这个流的写,读不会影响,所以出站只需要 unwrap 后的 read,入站只需要 unwrap 后的 write

Wait 不太需要单独 noise,就放 header-custom 的 UDP 里,可以加个选项 times,1 的话就是只发一次,带 delay 的话就是单独发

合一起也可以,我想的是可以在整个连接周期只发一次,或者可设置重置时间,感觉已经打通的四元组再发没啥意义

@RPRX
Copy link
Member

RPRX commented Feb 12, 2026

主要是都放 header-custom 里然后可以复用代码和文档吧

Splice 的话你可能没看 ReadFrom() 里的实现,Linux 要知道两端的 fd 才能对拷,不然实际上是基于 buffer 的 copy,没用到 Splice

现在 TCP 只有 header-custom 和 fragment 还是可以 unwarp 一下的

@LjhAUMEM
Copy link
Contributor Author

Splice 的话你可能没看 ReadFrom() 里的实现,Linux 要知道两端的 fd 才能对拷,不然实际上是基于 buffer 的 copy,没用到 Splice

现在 TCP 只有 header-custom 和 fragment 还是可以 unwarp 一下的

哦哦,确实没看,那以后会 break 了再说吧

header-custom 的话直接照抄 noises 吧有 str 方便些

都改成 str 吗,我怀疑 str 能否表示完全 0-255,还有对于中文不知道用的是啥编码

@LjhAUMEM
Copy link
Contributor Author

tcp 流粘包不太好实现,不知道后面会接收多少才会到下一个包头

@Fangliding
Copy link
Member

我看了一下 好像ack帧不受mtu限制 一个ACK帧会发出最多128个报文序号 一个序号4字节 最后加上帧头开销的一些零碎就是五百多

const ackNumberLimit = 128
func (s *AckSegment) Serialize(b []byte) {
	binary.BigEndian.PutUint16(b, s.Conv)
	b[2] = byte(CommandACK)
	b[3] = byte(s.Option)
	binary.BigEndian.PutUint32(b[4:], s.ReceivingWindow)
	binary.BigEndian.PutUint32(b[8:], s.ReceivingNext)
	binary.BigEndian.PutUint32(b[12:], s.Timestamp)
	b[16] = byte(len(s.NumberList))
	n := 17
	for _, number := range s.NumberList {
		binary.BigEndian.PutUint32(b[n:], number)
		n += 4
	}
}

@LjhAUMEM
Copy link
Contributor Author

LjhAUMEM commented Mar 2, 2026

要不 R 佬弄个青春版 TCP

不过他似乎是搭配 WireGuard 使用的

搭配这个跑不起来才对啊,何来的崩溃

当初就是 mask 不处理分片才能加的那么快

@RPRX
Copy link
Member

RPRX commented Mar 2, 2026

弄毛线青春版 TCP,把 mkcp 改一下让一个 ACK 帧遵循 MTU 不就行了,@LjhAUMEM 你这个 PR 顺便改一下吧

@LjhAUMEM
Copy link
Contributor Author

LjhAUMEM commented Mar 2, 2026

ecae787

你们也看看,除了有点丑应该没问题

我先去测试下

@RPRX
Copy link
Member

RPRX commented Mar 2, 2026

我感觉把 ackNumberLimit 改成变量然后根据 MTU 自动计算一下会更好?

@RPRX
Copy link
Member

RPRX commented Mar 2, 2026

如果说 ackNumberLimit 同时也是最大非连续包缓存数的话,降低它可能会影响吞吐量,但 MTU 都那么小了还要啥吞吐量

@Fangliding
Copy link
Member

mtu自动算一下AckNumberLimit就好 我也是这么想的

@RPRX
Copy link
Member

RPRX commented Mar 2, 2026

还有 mkcp 报文序号固定为 4 字节现在突然成了问题,其实 break 的话可以把它改成 protobuf 那个 varint

但是关于 break mkcp 以使它 header 更小的问题是一个系统性工程,以后有空再讨论吧

@RPRX
Copy link
Member

RPRX commented Mar 2, 2026

总之我有一个大胆的想法是以后把 mkcp break 掉改名 xkcp,毕竟现在分享链接里也没有 mtu,这 xdns 实质上还是不好分享

@RPRX
Copy link
Member

RPRX commented Mar 2, 2026

毕竟现在这 mkcp 数据包 18 字节 header,ack 包 16 字节 header,对于几十的 mtu 来说还是太奢侈了,改成几字节还差不多

@LjhAUMEM
Copy link
Contributor Author

LjhAUMEM commented Mar 2, 2026

force push 了一下 b5e5543

@RPRX
Copy link
Member

RPRX commented Mar 2, 2026

然后 mckp-* 那俩不上不下的 fm 也可以删了、换成 psk + 小 salt + xor,这些都是后话了

@RPRX
Copy link
Member

RPRX commented Mar 2, 2026

或者比如说本来后面的数据就是 random 的 VLESS Enc 那么可以拿后面的数据当 salt,结合 psk 去把前面的 header 给 xor 了

@RPRX
Copy link
Member

RPRX commented Mar 2, 2026

@LjhAUMEM 测了还会有超过预设 MTU 的情况吗

@LjhAUMEM
Copy link
Contributor Author

LjhAUMEM commented Mar 2, 2026

@LjhAUMEM 测了还会有超过预设 MTU 的情况吗

9424456 之后都没有了,就是写队列容易满稍微提升了一下,目前很流畅

@LjhAUMEM
Copy link
Contributor Author

LjhAUMEM commented Mar 3, 2026

clean 和 writeto 确实存在个 channel 竞态可能导致崩溃,已经修复

测试 8.8.8.8 53 看完了个 20 分钟的 youtube 视频,应该没问题了

@patterniha
Copy link
Collaborator

Currently, the dns-query-domain-length in many DNS in Iran is restricted, some to 151 and some to 99, and returns server-failure or nxdomain for longer lengths.
But dnstt/slipstream uses a domain with a longer length, so, dnstt/slipstream does not work on most Iran dns and there are only a few dns that dnstt/slipstream works on.

Currently, 90% of the 1% of people who are connected use dnstt/slipstream, But the number of remaining working DNSs is decreasing every day.

Therefore, it is important that the domain length can be limited in XDNS.

@patterniha
Copy link
Collaborator

Also, XDNS should use base32 case insensitive encoding/decoding: Mygod/slipstream-rust#1

@LjhAUMEM
Copy link
Contributor Author

LjhAUMEM commented Mar 3, 2026

If I remember correctly, the domain name should be case-insensitive.

encoded = bytes.ToLower(encoded)

encoded := bytes.ToUpper(bytes.Join(prefix, nil))

if !bytes.Equal(bytes.ToLower(aft[i]), bytes.ToLower(suffix[i])) {

You can control the domain name length by setting the MTU in the client.

@patterniha
Copy link
Collaborator

patterniha commented Mar 3, 2026

@LjhAUMEM

What is the minimum possible value of mtu?

Why does the doc still say it is 576???

Because the data is converted to base32 and also a part is used for the main domain itself, to use restricted-Iran-dns, we need to lower mtu to about 50.

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