mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 03:05:02 +03:00
fix(sub): look up a socket child's fallback front on the child's own host
projectThroughFallbackMaster found the front of a unix-socket child by scanning every inbound's settings.fallbacks for that socket. A node's inbound settings are mirrored from the node, so a node could name any socket and have its own port, security and external proxies substituted into the subscription links of another host's child. A socket exists only on the host that binds it, so the lookup, and the same scan the inbounds page uses for share links, now considers only inbounds hosted where the child runs.
This commit is contained in:
@@ -233,6 +233,57 @@ func TestSub_HostOverFallback(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The legacy "@socket" lookup stays on the child's own host: an older inbound
|
||||
// mirrored from another node that names the same socket is never the master.
|
||||
func TestSub_LegacyFallbackMasterStaysOnChildHost(t *testing.T) {
|
||||
foreign, own := 7, 8
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
nodeID *int
|
||||
}{
|
||||
{"local child", nil},
|
||||
{"node child", &own},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
seedSubDB(t)
|
||||
db := database.GetDB()
|
||||
for _, master := range []*model.Inbound{
|
||||
{
|
||||
UserId: 1, Tag: "foreign", Enable: true, Port: 44301, Protocol: model.VLESS, NodeID: &foreign,
|
||||
Settings: `{"clients":[],"decryption":"none","fallbacks":[{"dest":"@vless-ws"}]}`,
|
||||
StreamSettings: `{"network":"tcp","security":"none","externalProxy":[{"forceTls":"none","dest":"evil.example","port":8080}]}`,
|
||||
},
|
||||
{
|
||||
UserId: 1, Tag: "front", Enable: true, Port: 9443, Protocol: model.VLESS, NodeID: tc.nodeID,
|
||||
Settings: `{"clients":[],"decryption":"none","fallbacks":[{"dest":"@vless-ws"}]}`,
|
||||
StreamSettings: `{"network":"tcp","security":"tls","tlsSettings":{"serverName":"front.sni"}}`,
|
||||
},
|
||||
} {
|
||||
if err := db.Create(master).Error; err != nil {
|
||||
t.Fatalf("seed %s: %v", master.Tag, err)
|
||||
}
|
||||
}
|
||||
child := seedSubInbound(t, "s1", "child", 4441, 1, `{"network":"ws","security":"none"}`)
|
||||
if err := db.Model(&model.Inbound{}).Where("id = ?", child.Id).
|
||||
Updates(map[string]any{"listen": "@vless-ws", "node_id": tc.nodeID}).Error; err != nil {
|
||||
t.Fatalf("place child: %v", err)
|
||||
}
|
||||
|
||||
links, _, _, _, err := NewSubService("").GetSubs("s1", "req.example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("GetSubs: %v", err)
|
||||
}
|
||||
joined := strings.Join(links, "\n")
|
||||
if strings.Contains(joined, "evil.example") {
|
||||
t.Fatalf("another node's inbound was used as fallback master: %s", joined)
|
||||
}
|
||||
if !strings.Contains(joined, ":9443") || !strings.Contains(joined, "sni=front.sni") {
|
||||
t.Fatalf("the front on the child's host must be projected: %s", joined)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// #8 — a client only gets hosts for inbounds it is actually on (the
|
||||
// clients ⋈ client_inbounds ⋈ inbounds join), never arbitrary inbounds.
|
||||
func TestSub_HostsResolveViaClientInbounds(t *testing.T) {
|
||||
|
||||
+10
-2
@@ -827,7 +827,7 @@ func (s *SubService) indexStatsBySubId(subId string) {
|
||||
// when applicable. Covers both fallback mechanisms:
|
||||
// - panel-tracked: an inbound_fallbacks row where child_id = inbound.Id
|
||||
// - legacy unix-socket: inbound.Listen begins with "@" and some VLESS/
|
||||
// Trojan inbound's settings.fallbacks references that listen address
|
||||
// Trojan inbound on the same host names that socket in settings.fallbacks
|
||||
//
|
||||
// Returns true when a projection happened; sub services call this before
|
||||
// generating links so a child VLESS-WS bound to 127.0.0.1 emits the
|
||||
@@ -861,7 +861,15 @@ func (s *SubService) projectThroughFallbackMaster(inbound *model.Inbound) bool {
|
||||
|
||||
if master == nil && len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
|
||||
var m model.Inbound
|
||||
if err := db.Model(model.Inbound{}).
|
||||
// A unix socket exists only on the host that binds it, so only an inbound
|
||||
// hosted where the child runs (this panel, or the same node) can front it.
|
||||
q := db.Model(model.Inbound{})
|
||||
if inbound.NodeID != nil {
|
||||
q = q.Where("node_id = ?", *inbound.NodeID)
|
||||
} else {
|
||||
q = q.Where("node_id IS NULL")
|
||||
}
|
||||
if err := q.
|
||||
Where("JSON_TYPE(settings, '$.fallbacks') = 'array'").
|
||||
Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", inbound.Listen).
|
||||
First(&m).Error; err == nil {
|
||||
|
||||
Reference in New Issue
Block a user