diff --git a/database/db.go b/database/db.go index 785a4e96..974aaadc 100644 --- a/database/db.go +++ b/database/db.go @@ -67,7 +67,6 @@ func initModels() error { &model.ApiToken{}, &model.ClientRecord{}, &model.ClientInbound{}, - &model.InboundFallbackChild{}, } for _, mdl := range models { if err := db.AutoMigrate(mdl); err != nil { diff --git a/database/migrate_data.go b/database/migrate_data.go index afa6f8ed..f56df75a 100644 --- a/database/migrate_data.go +++ b/database/migrate_data.go @@ -35,7 +35,6 @@ func migrationModels() []any { &model.InboundClientIps{}, &model.ClientRecord{}, &model.ClientInbound{}, - &model.InboundFallbackChild{}, } } diff --git a/database/model/model.go b/database/model/model.go index bf135f72..6ca2bd03 100644 --- a/database/model/model.go +++ b/database/model/model.go @@ -26,7 +26,6 @@ const ( WireGuard Protocol = "wireguard" Hysteria Protocol = "hysteria" Hysteria2 Protocol = "hysteria2" - PortFallback Protocol = "portfallback" ) // IsHysteria returns true for both "hysteria" and "hysteria2". @@ -207,9 +206,6 @@ func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig { } listen = fmt.Sprintf("\"%v\"", listen) protocol := string(i.Protocol) - if i.Protocol == PortFallback { - protocol = string(VLESS) - } return &xray.InboundConfig{ Listen: json_util.RawMessage(listen), Port: i.Port, @@ -366,19 +362,6 @@ type ClientInbound struct { func (ClientInbound) TableName() string { return "client_inbounds" } -type InboundFallbackChild struct { - Id int `json:"id" gorm:"primaryKey;autoIncrement"` - MasterId int `json:"masterId" gorm:"index;not null;column:master_id"` - ChildId int `json:"childId" gorm:"index;not null;column:child_id"` - Name string `json:"name"` - Alpn string `json:"alpn"` - Path string `json:"path"` - Xver int `json:"xver"` - SortOrder int `json:"sortOrder" gorm:"default:0;column:sort_order"` -} - -func (InboundFallbackChild) TableName() string { return "inbound_fallback_children" } - func (c *Client) ToRecord() *ClientRecord { rec := &ClientRecord{ Email: c.Email, diff --git a/frontend/src/models/inbound.js b/frontend/src/models/inbound.js index 4ffa782e..f333c628 100644 --- a/frontend/src/models/inbound.js +++ b/frontend/src/models/inbound.js @@ -13,7 +13,6 @@ export const Protocols = { HTTP: 'http', TUNNEL: 'tunnel', TUN: 'tun', - PORTFALLBACK: 'portfallback', }; export const SSMethods = { @@ -1855,14 +1854,14 @@ export class Inbound extends XrayCommonClass { canEnableTls() { if (this.protocol === Protocols.HYSTERIA) return true; - if (![Protocols.VMESS, Protocols.VLESS, Protocols.PORTFALLBACK, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol)) return false; + if (![Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol)) return false; return ["tcp", "ws", "http", "grpc", "httpupgrade", "xhttp"].includes(this.network); } //this is used for xtls-rprx-vision canEnableTlsFlow() { if (((this.stream.security === 'tls') || (this.stream.security === 'reality')) && (this.network === "tcp")) { - return this.protocol === Protocols.VLESS || this.protocol === Protocols.PORTFALLBACK; + return this.protocol === Protocols.VLESS; } return false; } @@ -1877,12 +1876,12 @@ export class Inbound extends XrayCommonClass { } canEnableReality() { - if (![Protocols.VLESS, Protocols.PORTFALLBACK, Protocols.TROJAN].includes(this.protocol)) return false; + if (![Protocols.VLESS, Protocols.TROJAN].includes(this.protocol)) return false; return ["tcp", "http", "grpc", "xhttp"].includes(this.network); } canEnableStream() { - return [Protocols.VMESS, Protocols.VLESS, Protocols.PORTFALLBACK, Protocols.TROJAN, Protocols.SHADOWSOCKS, Protocols.HYSTERIA].includes(this.protocol); + return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS, Protocols.HYSTERIA].includes(this.protocol); } reset() { @@ -2456,8 +2455,7 @@ Inbound.Settings = class extends XrayCommonClass { static getSettings(protocol) { switch (protocol) { case Protocols.VMESS: return new Inbound.VmessSettings(protocol); - case Protocols.VLESS: - case Protocols.PORTFALLBACK: return new Inbound.VLESSSettings(protocol); + case Protocols.VLESS: return new Inbound.VLESSSettings(protocol); case Protocols.TROJAN: return new Inbound.TrojanSettings(protocol); case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings(protocol); case Protocols.TUNNEL: return new Inbound.TunnelSettings(protocol); @@ -2473,8 +2471,7 @@ Inbound.Settings = class extends XrayCommonClass { static fromJson(protocol, json) { switch (protocol) { case Protocols.VMESS: return Inbound.VmessSettings.fromJson(json); - case Protocols.VLESS: - case Protocols.PORTFALLBACK: return Inbound.VLESSSettings.fromJson(json); + case Protocols.VLESS: return Inbound.VLESSSettings.fromJson(json); case Protocols.TROJAN: return Inbound.TrojanSettings.fromJson(json); case Protocols.SHADOWSOCKS: return Inbound.ShadowsocksSettings.fromJson(json); case Protocols.TUNNEL: return Inbound.TunnelSettings.fromJson(json); diff --git a/frontend/src/pages/api-docs/endpoints.js b/frontend/src/pages/api-docs/endpoints.js index f6dc9a57..ff384a3f 100644 --- a/frontend/src/pages/api-docs/endpoints.js +++ b/frontend/src/pages/api-docs/endpoints.js @@ -150,27 +150,6 @@ export const sections = [ { name: 'data', in: 'body (form)', type: 'string', desc: 'JSON-encoded inbound payload.' }, ], }, - { - method: 'GET', - path: '/panel/api/inbounds/:id/fallbackChildren', - summary: 'List fallback child inbounds for a Port-with-Fallback master inbound. Each row links a master inbound to one child inbound plus optional SNI/ALPN/path/xver match criteria.', - params: [ - { name: 'id', in: 'path', type: 'number', desc: 'Master inbound ID.' }, - ], - response: - '{\n "success": true,\n "obj": [\n {\n "id": 1,\n "masterId": 10,\n "childId": 11,\n "name": "trojan.example.com",\n "alpn": "",\n "path": "",\n "xver": 0,\n "sortOrder": 0\n }\n ]\n}', - }, - { - method: 'POST', - path: '/panel/api/inbounds/:id/fallbackChildren', - summary: 'Replace the entire fallback-children set for a master inbound. Body is JSON. Triggers an Xray restart.', - params: [ - { name: 'id', in: 'path', type: 'number', desc: 'Master inbound ID.' }, - { name: 'children', in: 'body (json)', type: 'object[]', desc: 'Array of {childId, name, alpn, path, xver, sortOrder} entries.' }, - ], - body: '{\n "children": [\n { "childId": 11, "name": "trojan.example.com", "xver": 0 },\n { "childId": 12, "alpn": "h2", "sortOrder": 1 }\n ]\n}', - response: '{\n "success": true,\n "msg": "Inbound updated"\n}', - }, ], }, diff --git a/frontend/src/pages/clients/ClientBulkAddModal.vue b/frontend/src/pages/clients/ClientBulkAddModal.vue index 5b78111e..4c391872 100644 --- a/frontend/src/pages/clients/ClientBulkAddModal.vue +++ b/frontend/src/pages/clients/ClientBulkAddModal.vue @@ -69,7 +69,7 @@ const delayedExpireDays = computed({ }); const MULTI_CLIENT_PROTOCOLS = new Set([ - 'shadowsocks', 'vless', 'vmess', 'trojan', 'hysteria', 'hysteria2', 'portfallback', + 'shadowsocks', 'vless', 'vmess', 'trojan', 'hysteria', 'hysteria2', ]); const inboundOptions = computed(() => diff --git a/frontend/src/pages/clients/ClientFormModal.vue b/frontend/src/pages/clients/ClientFormModal.vue index 8f17a254..d8ef80eb 100644 --- a/frontend/src/pages/clients/ClientFormModal.vue +++ b/frontend/src/pages/clients/ClientFormModal.vue @@ -99,7 +99,7 @@ function gbToBytes(gb) { } const MULTI_CLIENT_PROTOCOLS = new Set([ - 'shadowsocks', 'vless', 'vmess', 'trojan', 'hysteria', 'hysteria2', 'portfallback', + 'shadowsocks', 'vless', 'vmess', 'trojan', 'hysteria', 'hysteria2', ]); const inboundOptions = computed(() => @@ -131,7 +131,7 @@ watch(showFlow, (next) => { const vlessLikeIds = computed(() => { const ids = new Set(); for (const row of props.inbounds || []) { - if (row && (row.protocol === 'vless' || row.protocol === 'portfallback')) { + if (row && row.protocol === 'vless') { ids.add(row.id); } } diff --git a/frontend/src/pages/inbounds/InboundFormModal.vue b/frontend/src/pages/inbounds/InboundFormModal.vue index 791522fa..ba74a1a9 100644 --- a/frontend/src/pages/inbounds/InboundFormModal.vue +++ b/frontend/src/pages/inbounds/InboundFormModal.vue @@ -114,85 +114,9 @@ const security = computed({ const isVlessLike = computed(() => { if (!inbound.value) return false; - return inbound.value.protocol === Protocols.VLESS - || inbound.value.protocol === Protocols.PORTFALLBACK; + return inbound.value.protocol === Protocols.VLESS; }); -const fallbackChildren = ref([]); -let fallbackChildRowKey = 0; - -const fallbackChildColumns = computed(() => [ - { title: t('pages.inbounds.portFallback.child') || 'Inbound', key: 'childId', width: '40%' }, - { title: 'SNI', key: 'name' }, - { title: 'ALPN', key: 'alpn' }, - { title: t('pages.inbounds.portFallback.path') || 'Path', key: 'path' }, - { title: 'xver', key: 'xver', width: 100 }, - { title: '', key: 'actions', width: 90 }, -]); - -const fallbackChildOptions = computed(() => { - const list = props.dbInbounds || []; - const masterId = props.dbInbound?.id; - return list - .filter((ib) => ib.id !== masterId && ib.protocol !== Protocols.PORTFALLBACK) - .map((ib) => ({ - label: `${ib.remark || `#${ib.id}`} ยท ${ib.protocol}:${ib.port}`, - value: ib.id, - })); -}); - -function addFallbackChild() { - fallbackChildren.value.push({ - rowKey: `row-${++fallbackChildRowKey}`, - childId: null, - name: '', - alpn: '', - path: '', - xver: 0, - }); -} - -function removeFallbackChild(idx) { - fallbackChildren.value.splice(idx, 1); -} - -async function loadFallbackChildren(masterId) { - fallbackChildren.value = []; - if (!masterId) return; - const msg = await HttpUtil.get(`/panel/api/inbounds/${masterId}/fallbackChildren`); - if (!msg?.success || !Array.isArray(msg.obj)) return; - fallbackChildren.value = msg.obj.map((r) => ({ - rowKey: `row-${++fallbackChildRowKey}`, - childId: r.childId, - name: r.name || '', - alpn: r.alpn || '', - path: r.path || '', - xver: r.xver || 0, - })); -} - -async function saveFallbackChildren(masterId) { - if (!masterId) return true; - const payload = { - children: fallbackChildren.value - .filter((c) => c.childId) - .map((c, i) => ({ - childId: c.childId, - name: c.name, - alpn: c.alpn, - path: c.path, - xver: Number(c.xver) || 0, - sortOrder: i, - })), - }; - const msg = await HttpUtil.post( - `/panel/api/inbounds/${masterId}/fallbackChildren`, - payload, - { headers: { 'Content-Type': 'application/json' } }, - ); - return !!msg?.success; -} - const canEnableStream = computed(() => inbound.value?.canEnableStream?.() === true); const canEnableTls = computed(() => inbound.value?.canEnableTls?.() === true); const canEnableReality = computed(() => inbound.value?.canEnableReality?.() === true); @@ -262,16 +186,10 @@ watch(() => props.open, (next) => { if (!next) return; if (props.mode === 'edit' && props.dbInbound) { loadFromDbInbound(props.dbInbound); - if (props.dbInbound.protocol === Protocols.PORTFALLBACK) { - loadFallbackChildren(props.dbInbound.id); - } else { - fallbackChildren.value = []; - } } else { inbound.value = makeFreshInbound(Protocols.VLESS); dbForm.value = freshDbForm(); primeAdvancedJson(); - fallbackChildren.value = []; } activeTabKey.value = 'basic'; advancedSectionKey.value = 'all'; @@ -737,14 +655,6 @@ async function submit() { : '/panel/api/inbounds/add'; const msg = await HttpUtil.post(url, payload); if (msg?.success) { - if (inbound.value.protocol === Protocols.PORTFALLBACK) { - const masterId = props.mode === 'edit' - ? props.dbInbound.id - : (msg.obj?.id || msg.obj?.Id); - if (masterId) { - await saveFallbackChildren(masterId); - } - } emit('saved'); close(); } @@ -859,41 +769,6 @@ watch(() => inbound.value?.protocol, () => stampAdvancedTextFor('stream')); - - - {{ t('pages.inbounds.portFallback.help') || 'Pick inbounds that should catch traffic this VLESS-TLS inbound does not match. Each child must listen on 127.0.0.1 to receive forwarded connections.' }} - - - - - - {{ t('add') }} - - - diff --git a/web/controller/inbound.go b/web/controller/inbound.go index 4d7bf609..f5280920 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -17,9 +17,8 @@ import ( // InboundController handles HTTP requests related to Xray inbounds management. type InboundController struct { - inboundService service.InboundService - xrayService service.XrayService - fallbackService service.FallbackService + inboundService service.InboundService + xrayService service.XrayService } // NewInboundController creates a new InboundController and sets up its routes. @@ -63,7 +62,6 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) { g.GET("/list", a.getInbounds) g.GET("/options", a.getInboundOptions) g.GET("/get/:id", a.getInbound) - g.GET("/:id/fallbackChildren", a.getFallbackChildren) g.POST("/add", a.addInbound) g.POST("/del/:id", a.delInbound) @@ -72,7 +70,6 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) { g.POST("/:id/resetTraffic", a.resetInboundTraffic) g.POST("/resetAllTraffics", a.resetAllTraffics) g.POST("/import", a.importInbound) - g.POST("/:id/fallbackChildren", a.setFallbackChildren) } // getInbounds retrieves the list of inbounds for the logged-in user. @@ -333,38 +330,3 @@ func resolveHost(c *gin.Context) string { return c.Request.Host } -func (a *InboundController) getFallbackChildren(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { - jsonMsg(c, I18nWeb(c, "get"), err) - return - } - rows, err := a.fallbackService.GetChildren(id) - if err != nil { - jsonMsg(c, I18nWeb(c, "get"), err) - return - } - jsonObj(c, rows, nil) -} - -func (a *InboundController) setFallbackChildren(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { - jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err) - return - } - type body struct { - Children []service.FallbackChildInput `json:"children"` - } - var b body - if err := c.ShouldBindJSON(&b); err != nil { - jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err) - return - } - if err := a.fallbackService.SetChildren(id, b.Children); err != nil { - jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err) - return - } - a.xrayService.SetToNeedRestart() - jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), nil) -} diff --git a/web/service/fallback.go b/web/service/fallback.go deleted file mode 100644 index 9a414ee9..00000000 --- a/web/service/fallback.go +++ /dev/null @@ -1,122 +0,0 @@ -package service - -import ( - "fmt" - "strings" - - "github.com/mhsanaei/3x-ui/v3/database" - "github.com/mhsanaei/3x-ui/v3/database/model" - - "gorm.io/gorm" -) - -type FallbackService struct{} - -type FallbackChildInput struct { - ChildId int `json:"childId"` - Name string `json:"name"` - Alpn string `json:"alpn"` - Path string `json:"path"` - Xver int `json:"xver"` - SortOrder int `json:"sortOrder"` -} - -func (s *FallbackService) GetChildren(masterId int) ([]model.InboundFallbackChild, error) { - var rows []model.InboundFallbackChild - err := database.GetDB(). - Where("master_id = ?", masterId). - Order("sort_order ASC, id ASC"). - Find(&rows).Error - if err != nil { - return nil, err - } - return rows, nil -} - -func (s *FallbackService) SetChildren(masterId int, children []FallbackChildInput) error { - db := database.GetDB() - return db.Transaction(func(tx *gorm.DB) error { - if err := tx.Where("master_id = ?", masterId).Delete(&model.InboundFallbackChild{}).Error; err != nil { - return err - } - for i, c := range children { - if c.ChildId <= 0 || c.ChildId == masterId { - continue - } - row := model.InboundFallbackChild{ - MasterId: masterId, - ChildId: c.ChildId, - Name: c.Name, - Alpn: c.Alpn, - Path: c.Path, - Xver: c.Xver, - SortOrder: c.SortOrder, - } - if row.SortOrder == 0 { - row.SortOrder = i - } - if err := tx.Create(&row).Error; err != nil { - return err - } - } - return nil - }) -} - -func (s *FallbackService) BuildFallbacksJSON(tx *gorm.DB, masterId int) ([]map[string]any, error) { - if tx == nil { - tx = database.GetDB() - } - var rows []model.InboundFallbackChild - err := tx.Where("master_id = ?", masterId). - Order("sort_order ASC, id ASC"). - Find(&rows).Error - if err != nil { - return nil, err - } - if len(rows) == 0 { - return nil, nil - } - - childIds := make([]int, 0, len(rows)) - for i := range rows { - childIds = append(childIds, rows[i].ChildId) - } - var children []model.Inbound - if err := tx.Where("id IN ?", childIds).Find(&children).Error; err != nil { - return nil, err - } - byId := make(map[int]*model.Inbound, len(children)) - for i := range children { - byId[children[i].Id] = &children[i] - } - - out := make([]map[string]any, 0, len(rows)) - for _, r := range rows { - child, ok := byId[r.ChildId] - if !ok { - continue - } - listen := strings.TrimSpace(child.Listen) - if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" { - listen = "127.0.0.1" - } - entry := map[string]any{ - "dest": fmt.Sprintf("%s:%d", listen, child.Port), - } - if r.Name != "" { - entry["name"] = r.Name - } - if r.Alpn != "" { - entry["alpn"] = r.Alpn - } - if r.Path != "" { - entry["path"] = r.Path - } - if r.Xver > 0 { - entry["xver"] = r.Xver - } - out = append(out, entry) - } - return out, nil -} diff --git a/web/service/inbound.go b/web/service/inbound.go index 15ff3af0..b3c27953 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -24,9 +24,8 @@ import ( ) type InboundService struct { - xrayApi xray.XrayAPI - clientService ClientService - fallbackService FallbackService + xrayApi xray.XrayAPI + clientService ClientService } func (s *InboundService) runtimeFor(ib *model.Inbound) (runtime.Runtime, error) { @@ -149,8 +148,8 @@ type InboundOption struct { // GetInboundOptions returns the picker-sized projection of the user's inbounds. // The TlsFlowCapable flag mirrors Inbound.canEnableTlsFlow() on the frontend -// (VLESS/PortFallback over TCP with tls or reality) so the client modal does -// not need StreamSettings to decide whether to show the Flow field. +// (VLESS over TCP with tls or reality) so the client modal does not need +// StreamSettings to decide whether to show the Flow field. func (s *InboundService) GetInboundOptions(userId int) ([]InboundOption, error) { db := database.GetDB() var rows []struct { @@ -182,9 +181,9 @@ func (s *InboundService) GetInboundOptions(userId int) ([]InboundOption, error) } // inboundCanEnableTlsFlow mirrors Inbound.canEnableTlsFlow() from the frontend: -// XTLS Vision is only valid for VLESS / PortFallback on TCP with tls or reality. +// XTLS Vision is only valid for VLESS on TCP with tls or reality. func inboundCanEnableTlsFlow(protocol, streamSettings string) bool { - if protocol != string(model.VLESS) && protocol != string(model.PortFallback) { + if protocol != string(model.VLESS) { return false } if streamSettings == "" { diff --git a/web/service/xray.go b/web/service/xray.go index 53afa6dd..f7024517 100644 --- a/web/service/xray.go +++ b/web/service/xray.go @@ -148,7 +148,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) { } entry := map[string]any{"email": c.Email} switch inbound.Protocol { - case model.VLESS, model.PortFallback: + case model.VLESS: if c.ID != "" { entry["id"] = c.ID } @@ -193,22 +193,6 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) { settings["clients"] = finalClients } - if inbound.Protocol == model.PortFallback { - fallbacks, fbErr := s.inboundService.fallbackService.BuildFallbacksJSON(nil, inbound.Id) - if fbErr != nil { - return nil, fbErr - } - generic := make([]any, 0, len(fallbacks)) - for _, f := range fallbacks { - generic = append(generic, f) - } - settings["fallbacks"] = generic - if _, ok := settings["decryption"]; !ok { - settings["decryption"] = "none" - } - mutated = true - } - if mutated { modifiedSettings, err := json.MarshalIndent(settings, "", " ") if err != nil { diff --git a/web/translation/en-US.json b/web/translation/en-US.json index b20e209e..a0fe3fa8 100644 --- a/web/translation/en-US.json +++ b/web/translation/en-US.json @@ -250,12 +250,6 @@ "node": "Node", "deployTo": "Deploy to", "localPanel": "Local panel", - "portFallback": { - "title": "Fallback children", - "help": "Pick inbounds that should catch traffic this VLESS-TLS inbound does not match. Each child must listen on 127.0.0.1 to receive forwarded connections.", - "child": "Inbound", - "path": "Path" - }, "protocol": "Protocol", "port": "Port", "portMap": "Port Mapping",