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('delete') }}
-
-
-
-
- {{ 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",