client_update_tunnel_peers_test.go 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. package service
  2. import (
  3. "encoding/base64"
  4. "testing"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  6. wgutil "github.com/mhsanaei/3x-ui/v3/internal/util/wireguard"
  7. )
  8. func wgTestKeypair(t *testing.T, seed byte) (priv, pub string) {
  9. t.Helper()
  10. raw := make([]byte, 32)
  11. for i := range raw {
  12. raw[i] = seed
  13. }
  14. priv = base64.StdEncoding.EncodeToString(raw)
  15. pub, err := wgutil.PublicKeyFromPrivate(priv)
  16. if err != nil {
  17. t.Fatalf("derive public key: %v", err)
  18. }
  19. return priv, pub
  20. }
  21. func inboundPeer(t *testing.T, inboundSvc *InboundService, ibId int, email string) model.Client {
  22. t.Helper()
  23. ib, err := inboundSvc.GetInbound(ibId)
  24. if err != nil {
  25. t.Fatalf("GetInbound %d: %v", ibId, err)
  26. }
  27. clients, err := inboundSvc.GetClients(ib)
  28. if err != nil {
  29. t.Fatalf("GetClients %d: %v", ibId, err)
  30. }
  31. for i := range clients {
  32. if clients[i].Email == email {
  33. return clients[i]
  34. }
  35. }
  36. t.Fatalf("email %q not found on inbound %d", email, ibId)
  37. return model.Client{}
  38. }
  39. // A client on several WireGuard inbounds is several independent peers, each
  40. // with its own keypair and tunnel address. The edit form can only carry one
  41. // field set, so a save that broadcasts it leaves every peer but one with keys
  42. // and an address belonging to a different node, breaking those tunnels.
  43. func TestUpdateDoesNotBroadcastPeerCredentialsAcrossTunnelInbounds(t *testing.T) {
  44. setupBulkDB(t)
  45. inboundSvc := &InboundService{}
  46. svc := &ClientService{}
  47. const email = "multi@wg"
  48. privA, pubA := wgTestKeypair(t, 0x11)
  49. privB, pubB := wgTestKeypair(t, 0x22)
  50. peerA := model.Client{
  51. Email: email, SubID: "sub-multi", Enable: true,
  52. PrivateKey: privA, PublicKey: pubA, AllowedIPs: []string{"10.10.151.5/32"},
  53. }
  54. peerB := model.Client{
  55. Email: email, SubID: "sub-multi", Enable: true,
  56. PrivateKey: privB, PublicKey: pubB, AllowedIPs: []string{"10.10.152.5/32"},
  57. }
  58. ibA := mkInbound(t, 51821, model.WireGuard, clientsSettings(t, []model.Client{peerA}))
  59. if err := svc.SyncInbound(nil, ibA.Id, []model.Client{peerA}); err != nil {
  60. t.Fatalf("seed inbound A linkage: %v", err)
  61. }
  62. ibB := mkInbound(t, 51822, model.WireGuard, clientsSettings(t, []model.Client{peerB}))
  63. if err := svc.SyncInbound(nil, ibB.Id, []model.Client{peerB}); err != nil {
  64. t.Fatalf("seed inbound B linkage: %v", err)
  65. }
  66. recId := lookupClientRecord(t, email).Id
  67. // What the client edit form sends: inbound A's peer fields, once, for
  68. // a save that only meant to change an unrelated field.
  69. updated := model.Client{
  70. Email: email, Enable: true, Comment: "renamed",
  71. PrivateKey: privA, PublicKey: pubA, AllowedIPs: []string{"10.10.151.5/32"},
  72. }
  73. if _, err := svc.Update(inboundSvc, recId, updated, 0); err != nil {
  74. t.Fatalf("Update: %v", err)
  75. }
  76. gotB := inboundPeer(t, inboundSvc, ibB.Id, email)
  77. if gotB.PrivateKey != privB || gotB.PublicKey != pubB {
  78. t.Fatalf("inbound B peer keys were overwritten with inbound A's: private=%q public=%q", gotB.PrivateKey, gotB.PublicKey)
  79. }
  80. if len(gotB.AllowedIPs) != 1 || gotB.AllowedIPs[0] != "10.10.152.5/32" {
  81. t.Fatalf("inbound B AllowedIPs = %v, want unchanged [10.10.152.5/32]", gotB.AllowedIPs)
  82. }
  83. gotA := inboundPeer(t, inboundSvc, ibA.Id, email)
  84. if gotA.PrivateKey != privA || len(gotA.AllowedIPs) != 1 || gotA.AllowedIPs[0] != "10.10.151.5/32" {
  85. t.Fatalf("inbound A peer must keep its own values, got %+v", gotA)
  86. }
  87. }