Skip to content

Conversation

kastov
Copy link
Contributor

@kastov kastov commented Aug 9, 2025

Hi there,

Currently it is almost impossible to do routing with only one Inbound.

Of course, you can use Dokedomo-Door > Routing > Vless > Routing > Out, but in this case you will still have to have several INCOMING Dokedomo-Doors if I need to route traffic somewhere else.

Simple example:

There is a server called TEST.
We have VLESS Reality, which is configured according to all generally accepted standards on port 443, there is no Nginx/Haproxy/etc in front of it.

And here is the task: from the TEST server to route traffic to the TEST_B, TEST_C servers.
This can be done quite simply, BUT then we will have two Inbound, and they cannot occupy port 443 at the same time.

On the other hand, we can put Nginx/Haproxy in front, as far as I know - putting something in front of Xray in the case of Reality is not recommended, from which I conclude that this method, although in theory it will be working, will be contrary to the principles of Reality.

This small PR adds the ability to define routing rules based on incoming SNI for Trojan+TLS, VLESS+Reality, VLESS+TLS.

With this routing, you will no longer need to create multiple inbounds on different ports if they are completely identical, and will also allow you to leave one single Reality on port 443 and at the same time have full routing capability based on SNI.

{
  "routing": {
    "rules": [
      {
        "type": "field",
        "incomingSni": ["domain:example.com", "sample.com"],
        "outboundTag": "BLOCK"
      }
    ],
    "domainStrategy": "AsIs"
  }
}

@Fangliding
Copy link
Member

in port in SNI in protocol in xxxxx.... 哎。。。
还有这样quic读不出

@Jolymmiles
Copy link
Contributor

in port in SNI in protocol in xxxxx.... 哎。。。 还有这样quic读不出

Could you explain pls?

@patterniha
Copy link
Collaborator

patterniha commented Aug 9, 2025

I think adding route-id to vless is better idea.

you want to allow the users to choose which server to connect to in the end.

but users have to change the sni to connect to another server! these two things are completely independent of each other.

Anyway, this is also a solution, but i think the correct way is to add route-id to vless.

@Jolymmiles
Copy link
Contributor

I think adding route-id to vless-options is better idea.

you want to allow the users to choose which server to connect to in the end.

but users have to change the sni to connect to another server! these two things are completely independent of each other.

Anyway, this is also a solution, but i think the correct way is to add route-id to vless option.

If user go change sni, user stopping be user. Sni provide proxy provider

@patterniha
Copy link
Collaborator

patterniha commented Aug 9, 2025

  1. users still can't choose the end-server in non-tls vless

  2. does not work on Xhttp

So I think this PR should be closed.

@RPRX is writing vless-encryption. ask him to write route-id for vless too.

@kastov
Copy link
Contributor Author

kastov commented Aug 9, 2025

  1. users still can't choose the end-server in non-tls vless
  2. does not work on Xhttp

So I think this PR should be closed.

@RPRX is writing vless-encryption-option. ask him to write route-id-vless-option too.

This feature mostly not for end-users, but for providers.

I don't think that routing-id is an option, because it will be break changing for all clients and other client cores.

@patterniha
Copy link
Collaborator

Anyway, your code is incomplete and does not support Xhttp.

@Jolymmiles
Copy link
Contributor

Anyway, your code is incomplete and does not support Xhttp.

Could you pls show where we should add it in xhhtp, I can't really find it(

@patterniha
Copy link
Collaborator

patterniha commented Aug 9, 2025

Anyway, your code is incomplete and does not support Xhttp.

Could you pls show where we should add it in xhhtp, I can't really find it(

conn := splitConn{
writer: httpSC,
reader: httpSC,
remoteAddr: remoteAddr,
localAddr: h.localAddr,
}

you need to add request.TLS.ServerName here, but if we have separate "downloadSettings" with separate "sni", which "sni" should be chosen???

So i close this PR, you should add route-id to vless.

@patterniha patterniha closed this Aug 9, 2025
@kastov
Copy link
Contributor Author

kastov commented Aug 9, 2025

Anyway, your code is incomplete and does not support Xhttp.

Could you pls show where we should add it in xhhtp, I can't really find it(

conn := splitConn{
writer: httpSC,
reader: httpSC,
remoteAddr: remoteAddr,
localAddr: h.localAddr,
}

you need to add request.TLS.ServerName, but if we have separate "downloadSettings" with different "sni", which "sni" should be chosen???

So i close this PR, you should add route-id to vless.

No, i don't think that route-id is good option. It will break all backward compability.

@kastov
Copy link
Contributor Author

kastov commented Aug 13, 2025

@patterniha could you clarify your previous comment. How in your opinion route-id should work? Seems like there is small misunderstanding for which need this type of routing should be used.

In my thought, SNI based routing is firstly for server side config, not user side.

In current version, Xray config looks like this:

{
  "log": {},
  "dns": {},
  "inbounds": [
    {
      "tag": "VLESS_IN_1",
      "port": 443,
      "listen": "0.0.0.0",
      "protocol": "vless",
      "settings": {
        "clients": []
      },
      "sniffing": {
        "enabled": true,
        "destOverride": [
          "http",
          "tls"
        ]
      },
      "streamSettings": {
        "network": "raw",
        "security": "reality"
      }
    },
    {
      "tag": "VLESS_IN_2",
      "port": 8443,
      "listen": "0.0.0.0",
      "protocol": "vless",
      "settings": {
        "clients": []
      },
      "sniffing": {
        "enabled": true,
        "destOverride": [
          "http",
          "tls"
        ]
      },
      "streamSettings": {
        "network": "raw",
        "security": "reality"
      }
    }
  ],
  "outbounds": [
    {
      "tag": "DIRECT",
      "protocol": "freedom"
    },
    {
      "tag": "BLOCK",
      "protocol": "blackhole"
    },
    {
      "tag": "BRIDGE_SS_OUT_US",
      "protocol": "shadowsocks",
      "settings": {}
    },
    {
      "tag": "BRIDGE_SS_OUT_DE",
      "protocol": "shadowsocks",
      "settings": {}
    }
  ],
  "routing": {
    "rules": [
      {
        "type": "field",
        "inboundTag": [
          "VLESS_IN_1"
        ],
        "outboundTag": "BRIDGE_SS_OUT_US"
      },
      {
        "type": "field",
        "inboundTag": [
          "VLESS_IN_2"
        ],
        "outboundTag": "BRIDGE_SS_OUT_DE"
      }
    ],
    "domainStrategy": "AsIs"
  }
}

Both inbounds here is completely identical to each other, except TAG.

And, for example, end-user will have Xray-JSON subscription with this proxies (example values used)

  1. 🏴‍☠️ Unkown (BRIDGE IN > US OUT), bridge-in.example.com:443, reality, sni us.selfsteal.com
  2. 🏴‍☠️ Unkown (BRIDGE IN > DE OUT), bridge-in.example.com:8443, reality, sni de.selfsteal.com
  3. 🎯 Another Proxy (XX)

So user selects from two servers, which already preconfigured. It is called Subscription format, you can use it with Happ, v2rayNG or other apps.

And, currently we have no other options that clone inbounds and use other ports for each, which completely break reality security.
Ok, technically, if you don't want to break reality, you can use Haproxy/Nginx in front of Xray, but still.. inside Nginx/Haproxy you will be route to inbounds based on SNI. So why core can't handle it?

So, if we will have SNI based routing, we can simplify Xray server config to this:

{
  "log": {},
  "dns": {},
  "inbounds": [
    {
      "tag": "VLESS_IN_1",
      "port": 443,
      "listen": "0.0.0.0",
      "protocol": "vless",
      "settings": {
        "clients": []
      },
      "sniffing": {
        "enabled": true,
        "destOverride": [
          "http",
          "tls"
        ]
      },
      "streamSettings": {
        "network": "raw",
        "security": "reality"
      }
    }
  ],
  "outbounds": [
    {
      "tag": "DIRECT",
      "protocol": "freedom"
    },
    {
      "tag": "BLOCK",
      "protocol": "blackhole"
    },
    {
      "tag": "BRIDGE_SS_OUT_US",
      "protocol": "shadowsocks",
      "settings": {}
    },
    {
      "tag": "BRIDGE_SS_OUT_DE",
      "protocol": "shadowsocks",
      "settings": {}
    }
  ],
  "routing": {
    "rules": [
      {
        "type": "field",
        "incomingSni": [
          "us.selfsteal.com"
        ],
        "outboundTag": "BRIDGE_SS_OUT_US"
      },
      {
        "type": "field",
        "incomingSni": [
          "de.selfsteal.com"
        ],
        "outboundTag": "BRIDGE_SS_OUT_DE"
      }
    ],
    "domainStrategy": "AsIs"
  }
}

We don't need seconds inbound now, Reality is completely safe and we don't need nginx/haproxy in front of Xray for now.

And, Xray-Json subscription will looks like this. Bridge hosts completely identical now, except SNI.

  1. 🏴‍☠️ Unkown (BRIDGE IN > US OUT), bridge-in.example.com:443, reality, sni us.selfsteal.com
  2. 🏴‍☠️ Unkown (BRIDGE IN > DE OUT), bridge-in.example.com:443, reality, sni de.selfsteal.com
  3. 🎯 Another Proxy (XX)

@patterniha
Copy link
Collaborator

but if we have separate "downloadSettings" with different "sni", which "sni" should be chosen???

Each request can have two incoming-sni or none at all, so your solution is very limited.
I think limited server-side features should not be added to the core, I saw that @Fangliding also disagreed, that's why I closed it.
Anyway, you can ask @RPRX

@kastov
Copy link
Contributor Author

kastov commented Aug 13, 2025

@RPRX Hi there, can you please take a look to this PR and discussion? Thanks.

@RPRX
Copy link
Member

RPRX commented Aug 13, 2025

等我弄完 VLESS 加密的 XOR 再看

@RPRX
Copy link
Member

RPRX commented Aug 14, 2025

初衷是好的,只是实现方式有点歪

如果你想通过 SNI 来路由,应当在 TLS/REALITY 那部分代码取出 SNI 存进 ctx,然后由路由读取,这与 VLESS 等协议的代码无关

至于 route-id,创建多个 UUID 并通过邮箱来路由可以实现等价的效果,不过可能要增强下邮箱匹配,机场也得合并流量统计

@RPRX
Copy link
Member

RPRX commented Aug 14, 2025

SNI 路由有局限性,route-id 路由要改协议且不适用于其它协议,虽然我们主推 VLESS 但也得考虑下 Xray-core 的整体功能

我觉得最好的方式是,比如为同一个邮箱创建两个 UUID,然后仅服务端加一个 route-id,然后路由加个匹配

@patterniha 最近有空的话写一下吧

@RPRX
Copy link
Member

RPRX commented Aug 14, 2025

@patterniha 你可以先改 VMess 等其它协议的试一下,VLESS 的等我合了加密你再 rebase,不然 conflict

@Fangliding
Copy link
Member

那不就是email路由么

@RPRX
Copy link
Member

RPRX commented Aug 14, 2025

不是,email 相同,UUID 不同,流量直接统计到同一个用户,仅服务端多加个 route-id 字段,不需要改协议

@RPRX
Copy link
Member

RPRX commented Aug 14, 2025

  "clients": [
    {
      "id": "uuid1",
      "email": "user1",
      "route": "one"
    },
    {
      "id": "uuid2",
      "email": "user1",
      "route": "two"
    },
    {
      "id": "uuid3",
      "email": "user2",
      "route": "one"
    },
    {
      "id": "uuid4",
      "email": "user2",
      "route": "two"
    }
  ]

@RPRX
Copy link
Member

RPRX commented Aug 14, 2025

相当于 UUID 有了 route-id 的功效,不改协议,极端一点的话根据 UUID 最后几字节来路由,但不适用于非 UUID 协议和 UUIDv5

@RPRX
Copy link
Member

RPRX commented Aug 14, 2025

我觉得这个功能还是挺有用的,就像我说的,初衷是好的,只是这个 PR 的实现方式有点歪

@Fangliding
Copy link
Member

Fangliding commented Aug 14, 2025

对他们的破面板其实还是没用 因为他们的user和email都是一个萝卜一个坑的 考虑这些人需求就是灾难

@RPRX
Copy link
Member

RPRX commented Aug 14, 2025

Core 加了后面板可以适配,这里 email 是没变的,还是同一个用户,创建多个 UUID 而已

@Jolymmiles
Copy link
Contributor

SNI 路由有局限性,route-id 路由要改协议且不适用于其它协议,虽然我们主推 VLESS 但也得考虑下 Xray-core 的整体功能

我觉得最好的方式是,比如为同一个邮箱创建两个 UUID,然后仅服务端加一个 route-id,然后路由加个匹配

@patterniha 最近有空的话写一下吧

Could you please provide more information about the problems with SNI routing?

@RPRX
Copy link
Member

RPRX commented Aug 14, 2025

Could you please provide more information about the problems with SNI routing?

不适用于非 TLS/REALITY

@patterniha
Copy link
Collaborator

patterniha commented Aug 14, 2025

@RPRX

for your solution, we need to add a rule for each UUID, and we need to change routing-rules after adding/removing users, this is not elegant --> was wrong

///

we can add useIdAsRoute bool option, if true, the last-UUID-byte is ignored for user-verification and it is used for route-id.

for example if UUID is "11111111-1111-1111-1111-111111111111" and if useIdAsRoute is true:

all 11111111-1111-1111-1111-1111111111X is verified for user-verification and X is set to route-id.

and if useIdAsRoute is false, everything is the same as before.

///

"route-id": int (0-255)

{
"route-id": 5,
"outboundTag": "out-5"
}

@kastov
Copy link
Contributor Author

kastov commented Aug 17, 2025

2025/08/17 17:20:06.170684 [Info] [733749254] proxy/vless/inbound: firstLen = 1186
2025/08/17 17:20:06.170892 [Info] [733749254] proxy/vless/inbound: received request for tcp:cloudflare-dns.com:443
2025/08/17 17:20:06.171248 from [Masked IPv4]:55651 accepted tcp:cloudflare-dns.com:443 [INBOUND_TEST -> DIRECT] email: admin
2025/08/17 17:20:06.171713 [Info] [733749254] app/dispatcher: taking detour [DIRECT] for [tcp:cloudflare-dns.com:443]
2025/08/17 17:20:06.171788 [Info] [733749254] transport/internet/tcp: dialing TCP to tcp:cloudflare-dns.com:443
2025/08/17 17:20:06.171867 [Debug] [733749254] transport/internet: dialing to tcp:cloudflare-dns.com:443
2025/08/17 17:20:06.179148 [Info] [733749254] proxy/freedom: connection opened to tcp:cloudflare-dns.com:443, local endpoint [Masked IPv4]:46574, remote endpoint [Masked IPv4]:443
2025/08/17 17:20:06.186638 [Info] [733749254] proxy: XtlsPadding 316 150 0
2025/08/17 17:20:06.221141 [Info] [733749254] app/proxyman/inbound: connection ends > proxy/vless/inbound: connection ends > proxy/vless/inbound: failed to transfer request payload > io: read/write on closed pipe

@RPRX
Copy link
Member

RPRX commented Aug 17, 2025

是不是 XTLS Vision 里有 UUID 参与计算啥的 @yuhan6665

@RPRX
Copy link
Member

RPRX commented Aug 17, 2025

我想起来了,确实有,我改下 UUID 的获取方式吧

@RPRX
Copy link
Member

RPRX commented Aug 17, 2025

我改好了,话说是否有必要要求原始 UUID 最后一字节为 0,才可以使用这一功能?

@kastov
Copy link
Contributor Author

kastov commented Aug 17, 2025

我改好了,话说是否有必要要求原始 UUID 最后一字节为 0,才可以使用这一功能?

I believe it is not necessary

@RPRX
Copy link
Member

RPRX commented Aug 17, 2025

请测试 5464862

@kastov
Copy link
Contributor Author

kastov commented Aug 17, 2025

Working as intended 👍 Thanks!

Tested with VLESS+REALITY. Sadly currently don't have VLESS+TLS for testing.

@gfw-killer
Copy link

gfw-killer commented Aug 17, 2025

If this feature is to be used, the best practice is to build a new inbound station with the last byte of each user’s UUID set to 0, which is not a problem for newly deployed nodes

Modern Panels and Clients are based on Subscription system, panel can use a full random uuid in server inbound, but in the client subscription set the last byte to 0 or any other vlessRoute
people will update the subscription, there must be no problem

@RPRX
Copy link
Member

RPRX commented Aug 17, 2025

只是建议,我觉得这样的话会更清晰一些,总之该怎么设置 vlessRoute 以及订阅中的 UUID 该是什么已经完全是面板的事情了

@darthstren
Copy link

darthstren commented Aug 18, 2025

属于新功能,这样设计会不会更好一些

{
  "settings": {
    "flow": "xtls-rprx-vision",
    "clients": [
      {
        "id": "c481e5e7-2796-491d-ac55-00e3c1e0acc3",
		"ids": ["c481e5e7-2796-491d-ac55-00e3c1e0axc1", "c481e5e7-2796-491d-ac55-00e1e0acc43c", "c481e5e7-2796-491d-ac55-00c1e0acc5e3"]
        "email": "client_1"
      },
      {
        "id": "0022cf3a-77e2-4daf-9b5a-f76b7c5bb2e8",
		"ids": ["c481e5e7-2796-491d-ac55-f76b7c5bb2e8", "c481e5e7-2796-491d-ac55-f76b7c5cb2e8", "c481e5e7-2796-491d-ac55-f76b7z5bb2e8"]
        "email": "client_2"
      }
    ],
    "decryption": "none"
  }
}



{
  "id": "c481e5e7-2796-491d-ac55-00e3c1e0acc3",
  "outboundTag": "out-xx1"
}
{
  "id": "c481e5e7-2796-491d-ac55-00e1e0acc43c",
  "outboundTag": "out-xx2"
}
{
  "id": "0022cf3a-77e2-4daf-9b5a-f76b7c5bb2e8",
  "outboundTag": "out-xx3"
}

或者设计成

{
  "settings": {
    "flow": "xtls-rprx-vision",
    "clients": [
      {
       // 多ID场景
        "id": ["c481e5e7-2796-491d-ac55-00e3c1e0axc1", "c481e5e7-2796-491d-ac55-00e1e0acc43c", "c481e5e7-2796-491d-ac55-00c1e0acc5e3"]
        "email": "client_1"
      },
      {
        // 单ID场景
        "id": "c481e5e7-2796-491d-ac55-f76b7c5bb2e8"
        "email": "client_2"
      }
    ],
    "decryption": "none"
  }
}

@darthstren
Copy link

darthstren commented Aug 18, 2025

{
  "settings": {
    "flow": "xtls-rprx-vision",
    "routeId": true,
    "clients": [
      {
        "id": "c481e5e7-2796-491d-ac55-00e3c1e0acc3",
               c481e5e7-2796-491d-ac55-00e3c1e0ac01  // routeId = 1
               c481e5e7-2796-491d-ac55-00e3c1e0ac02  // routeId = 2
               c481e5e7-2796-491d-ac55-00e3c1e0ac03  // routeId = 3
               c481e5e7-2796-491d-ac55-00e3c1e0ac04  // routeId = 4
               c481e5e7-2796-491d-ac55-00e3c1e0ac05  // routeId = 5
        "email": "client_1"
      },
      {
        "id": "0022cf3a-77e2-4daf-9b5a-f76b7c5bb2e8",
               0022cf3a-77e2-4daf-9b5a-f76b7c5bb201  // routeId = 1
               0022cf3a-77e2-4daf-9b5a-f76b7c5bb202  // routeId = 2
               0022cf3a-77e2-4daf-9b5a-f76b7c5bb203  // routeId = 3
               0022cf3a-77e2-4daf-9b5a-f76b7c5bb204  // routeId = 4
               0022cf3a-77e2-4daf-9b5a-f76b7c5bb205  // routeId = 5
        "email": "client_2"
      }
    ],
    "decryption": "none"
  }
}


{
  "routeId": 5, // client_1
  "outboundTag": "out-5"
}

{
  "routeId": 5, // client_2
  "outboundTag": "out-5"
}

如果 routeId 为5,如何区分用户是client_1还是client_2,需要配合 email 属性嘛?

c481e5e7-2796-491d-ac55-00e3c1e0ac05
0022cf3a-77e2-4daf-9b5a-f76b7c5bb205

// RuleObject
{
  "user": ["client_1"],
  "routeId": 5, // client_1
  "outboundTag": "out-5-client_1"
}

{
  "user": ["client_2"],
  "routeId": 5, // client_2
  "outboundTag": "out-5-client_2"
}

@RPRX
Copy link
Member

RPRX commented Aug 18, 2025

@darthstren 你这配置太复杂了,他们重复个 flow 都想着精简一下 #5023

如果要区分用户,现在可以靠 email 来区分,不过暂时没有这个需求,他们的需求是对所有用户都生效,就像 incomingSNI 分流

提醒各位他发的不是配置示例,要使用这个功能,配置项只需给 routing rulesvlessRoute

@darthstren
Copy link

明白了,还以为在讨论给一个email可以路由多个outbound呢

@RPRX
Copy link
Member

RPRX commented Aug 18, 2025

又想了下,vlessRoute 换用 user-sent VLESS UUID's 7th<<8 | 8th bytes 7f300db

因为本来就是按 port 来处理的,支持两个字节,万一一个入口机、上千个出口呢,且如果写 0XXX,旧的 UUID 根本不会撞上

RPRX added a commit that referenced this pull request Aug 18, 2025
@kastov
Copy link
Contributor Author

kastov commented Aug 18, 2025

Tested, seems like works correctly

RPRX pushed a commit that referenced this pull request Aug 19, 2025
…elligent "useIP"/"ForceIP", enhance "origin" functionality (#5030)

#5009 (comment)
patterniha added a commit that referenced this pull request Aug 19, 2025
…elligent "useIP"/"ForceIP", enhance "origin" functionality (#5030)

#5009 (comment)
@gfw-killer
Copy link

Now that routing to different Outbounds made easy by vlessRoute
shouldn't we be able to use a different built-in DNS server for each Outbound? #4987 @RPRX

maoxikun pushed a commit to maoxikun/Xray-core that referenced this pull request Aug 23, 2025
maoxikun pushed a commit to maoxikun/Xray-core that referenced this pull request Aug 23, 2025
maoxikun pushed a commit to maoxikun/Xray-core that referenced this pull request Aug 23, 2025
@MK0ltra
Copy link

MK0ltra commented Aug 29, 2025

Can we have some updated documents? Have no idea how this thing works.

@RPRX
Copy link
Member

RPRX commented Aug 29, 2025

Can we have some updated documents? Have no idea how this thing works.

https://xtls.github.io/config/routing.html#ruleobject

@RPRX
Copy link
Member

RPRX commented Aug 30, 2025

说明一下,不要求原始 UUID 那四个地方为 0,设置 vlessRoute 时避开 4000-5fff 也就是避开 16384-24575 这个范围就行

@RPRX
Copy link
Member

RPRX commented Aug 30, 2025

不要求原始 UUID 那四个地方为 0

这一设计是为了服务端可以在任何时候开始用 vlessRoute 且兼容尚未更新配置的客户端,因为无需变更原始 UUID

@RPRX
Copy link
Member

RPRX commented Aug 30, 2025

且兼容尚未更新配置的客户端

想了下能连上,但是会落到 vlessRoute 16384-24575 这个范围内,所以还是得避开这个范围,那就没必要多一步要求原始的为 0

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.

10 participants