inbound_enable_port_test.go 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. package service
  2. import (
  3. "strings"
  4. "testing"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database"
  6. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  7. "github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
  8. )
  9. func setupEnablePortTest(t *testing.T) {
  10. t.Helper()
  11. setupConflictDB(t)
  12. mgr := runtime.NewManager(runtime.LocalDeps{APIPort: func() int { return 0 }})
  13. mgr.SetLocalRuntimeOverride(&fakeNodeRuntime{})
  14. runtime.SetManager(mgr)
  15. t.Cleanup(func() { runtime.SetManager(nil) })
  16. }
  17. func disableInboundRow(t *testing.T, id int) {
  18. t.Helper()
  19. if err := database.GetDB().Model(&model.Inbound{}).Where("id = ?", id).Update("enable", false).Error; err != nil {
  20. t.Fatalf("disable row %d: %v", id, err)
  21. }
  22. }
  23. // Saving a row while it is disabled skips every port guard, so enabling it later
  24. // was the one path that could still put two inbounds on one socket.
  25. func TestSetInboundEnableRefusesAPortAnotherEnabledInboundServes(t *testing.T) {
  26. setupEnablePortTest(t)
  27. seedInboundConflict(t, "holder", "0.0.0.0", 44431, model.VLESS, `{"network":"tcp"}`, `{}`)
  28. seedInboundConflict(t, "sleeper", "0.0.0.0", 44431, model.VLESS, `{"network":"tcp"}`, `{}`)
  29. sleeper := loadInboundByTag(t, "sleeper")
  30. disableInboundRow(t, sleeper.Id)
  31. _, err := (&InboundService{}).SetInboundEnable(sleeper.Id, true)
  32. if err == nil {
  33. t.Fatal("enabling a row onto a port another enabled inbound serves must be refused")
  34. }
  35. if !strings.Contains(err.Error(), "holder") {
  36. t.Fatalf("the refusal must name the row that owns the port; got %q", err)
  37. }
  38. if after := loadInboundByTag(t, "sleeper"); after.Enable {
  39. t.Fatal("a refused enable must not write the flag")
  40. }
  41. }
  42. // The enable path must keep the tcp/udp coexistence the rest of the guards
  43. // allow, or it would refuse half of the working setups out there.
  44. func TestSetInboundEnableAllowsTCPUDPCoexistence(t *testing.T) {
  45. setupEnablePortTest(t)
  46. seedInboundConflict(t, "udp-holder", "0.0.0.0", 44432, model.Hysteria, ``, `{}`)
  47. seedInboundConflict(t, "tcp-sleeper", "0.0.0.0", 44432, model.VLESS, `{"network":"tcp"}`, `{}`)
  48. sleeper := loadInboundByTag(t, "tcp-sleeper")
  49. disableInboundRow(t, sleeper.Id)
  50. if _, err := (&InboundService{}).SetInboundEnable(sleeper.Id, true); err != nil {
  51. t.Fatalf("a tcp inbound must be able to enable onto a udp-only row's port: %v", err)
  52. }
  53. if after := loadInboundByTag(t, "tcp-sleeper"); !after.Enable {
  54. t.Fatal("the row must be enabled")
  55. }
  56. }
  57. // Nodes run their own Xray, so a node row sharing a local port is legal and must
  58. // stay enableable.
  59. func TestSetInboundEnableAllowsANodeRowOnALocalPort(t *testing.T) {
  60. setupEnablePortTest(t)
  61. seedInboundConflict(t, "local-holder", "0.0.0.0", 44433, model.VLESS, `{"network":"tcp"}`, `{}`)
  62. node := &model.Node{Name: "n1", Address: "127.0.0.1", Port: 2096, Scheme: "https", Enable: true, Status: "online"}
  63. if err := database.GetDB().Create(node).Error; err != nil {
  64. t.Fatalf("seed node: %v", err)
  65. }
  66. seedInboundConflictNode(t, "node-row", "0.0.0.0", 44433, model.VLESS, `{"network":"tcp"}`, `{}`, &node.Id)
  67. row := loadInboundByTag(t, "node-row")
  68. disableInboundRow(t, row.Id)
  69. if _, err := (&InboundService{}).SetInboundEnable(row.Id, true); err != nil {
  70. t.Fatalf("a node row must be enableable regardless of a local row's port: %v", err)
  71. }
  72. }