api_shadowsocks_test.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. package xray
  2. import (
  3. "strings"
  4. "testing"
  5. "github.com/xtls/xray-core/proxy/shadowsocks"
  6. "github.com/xtls/xray-core/proxy/shadowsocks_2022"
  7. "google.golang.org/protobuf/proto"
  8. )
  9. // decodeSSAccount decodes the typed message buildUserAccount produced for a
  10. // shadowsocks user. The type URL is what the running inbound casts on, so the
  11. // test asserts on it directly.
  12. func decodeSSAccount(t *testing.T, user map[string]any) (typeURL string, legacy *shadowsocks.Account, modern *shadowsocks_2022.Account) {
  13. t.Helper()
  14. tm, err := buildUserAccount("shadowsocks", user)
  15. if err != nil {
  16. t.Fatalf("buildUserAccount: %v", err)
  17. }
  18. if tm == nil {
  19. t.Fatal("buildUserAccount returned no account for shadowsocks")
  20. }
  21. typeURL = tm.Type
  22. switch {
  23. case strings.Contains(typeURL, "shadowsocks_2022"):
  24. modern = new(shadowsocks_2022.Account)
  25. if err := proto.Unmarshal(tm.Value, modern); err != nil {
  26. t.Fatalf("unmarshal shadowsocks_2022 account: %v", err)
  27. }
  28. case strings.Contains(typeURL, "shadowsocks"):
  29. legacy = new(shadowsocks.Account)
  30. if err := proto.Unmarshal(tm.Value, legacy); err != nil {
  31. t.Fatalf("unmarshal shadowsocks account: %v", err)
  32. }
  33. default:
  34. t.Fatalf("unexpected account type %q", typeURL)
  35. }
  36. return typeURL, legacy, modern
  37. }
  38. func TestBuildUserAccountShadowsocksLegacyCiphers(t *testing.T) {
  39. tests := []struct {
  40. cipher string
  41. want shadowsocks.CipherType
  42. }{
  43. {"aes-128-gcm", shadowsocks.CipherType_AES_128_GCM},
  44. {"aead_aes_128_gcm", shadowsocks.CipherType_AES_128_GCM},
  45. {"aes-256-gcm", shadowsocks.CipherType_AES_256_GCM},
  46. {"AES-256-GCM", shadowsocks.CipherType_AES_256_GCM},
  47. {"aead_aes_256_gcm", shadowsocks.CipherType_AES_256_GCM},
  48. {"chacha20-poly1305", shadowsocks.CipherType_CHACHA20_POLY1305},
  49. {"chacha20-ietf-poly1305", shadowsocks.CipherType_CHACHA20_POLY1305},
  50. {"aead_chacha20_poly1305", shadowsocks.CipherType_CHACHA20_POLY1305},
  51. {"xchacha20-poly1305", shadowsocks.CipherType_XCHACHA20_POLY1305},
  52. {"xchacha20-ietf-poly1305", shadowsocks.CipherType_XCHACHA20_POLY1305},
  53. {"aead_xchacha20_poly1305", shadowsocks.CipherType_XCHACHA20_POLY1305},
  54. }
  55. for _, tt := range tests {
  56. t.Run(tt.cipher, func(t *testing.T) {
  57. user := map[string]any{"email": "[email protected]", "password": "pw", "cipher": tt.cipher}
  58. typeURL, legacy, modern := decodeSSAccount(t, user)
  59. if modern != nil {
  60. t.Fatalf("cipher %q built a shadowsocks-2022 account (%s); the legacy inbound casts to *shadowsocks.MemoryAccount and panics the core", tt.cipher, typeURL)
  61. }
  62. if legacy.CipherType != tt.want {
  63. t.Fatalf("CipherType = %v, want %v", legacy.CipherType, tt.want)
  64. }
  65. if legacy.Password != "pw" {
  66. t.Fatalf("Password = %q, want %q", legacy.Password, "pw")
  67. }
  68. })
  69. }
  70. }
  71. func TestBuildUserAccountShadowsocks2022Ciphers(t *testing.T) {
  72. for _, cipher := range []string{
  73. "2022-blake3-aes-128-gcm",
  74. "2022-blake3-aes-256-gcm",
  75. "2022-blake3-chacha20-poly1305",
  76. } {
  77. t.Run(cipher, func(t *testing.T) {
  78. user := map[string]any{"email": "[email protected]", "password": b64Key(7), "cipher": cipher}
  79. typeURL, _, modern := decodeSSAccount(t, user)
  80. if modern == nil {
  81. t.Fatalf("cipher %q built a legacy account (%s), want shadowsocks-2022", cipher, typeURL)
  82. }
  83. if modern.Key != b64Key(7) {
  84. t.Fatalf("Key = %q, want the client password", modern.Key)
  85. }
  86. })
  87. }
  88. }
  89. // TestBuildUserAccountShadowsocksReadsMethodKey covers the client maps taken
  90. // verbatim from an inbound's settings (the auto-renew path): they carry the
  91. // inbound's cipher under "method", never "cipher".
  92. func TestBuildUserAccountShadowsocksReadsMethodKey(t *testing.T) {
  93. user := map[string]any{"email": "[email protected]", "password": "pw", "method": "aes-256-gcm"}
  94. _, legacy, modern := decodeSSAccount(t, user)
  95. if modern != nil {
  96. t.Fatal("a client carrying only \"method\" must still build a legacy account")
  97. }
  98. if legacy.CipherType != shadowsocks.CipherType_AES_256_GCM {
  99. t.Fatalf("CipherType = %v, want AES_256_GCM", legacy.CipherType)
  100. }
  101. }
  102. func TestBuildUserAccountShadowsocksCipherKeyWins(t *testing.T) {
  103. user := map[string]any{
  104. "email": "[email protected]",
  105. "password": b64Key(3),
  106. "cipher": "2022-blake3-aes-128-gcm",
  107. "method": "aes-256-gcm",
  108. }
  109. _, _, modern := decodeSSAccount(t, user)
  110. if modern == nil {
  111. t.Fatal("the explicit \"cipher\" must win over a stale \"method\"")
  112. }
  113. }
  114. // TestBuildUserAccountShadowsocksUnknownCipherErrors is the regression for the
  115. // account-type guess that killed the core: an unrecognized cipher used to fall
  116. // through to a shadowsocks-2022 account, which xray's legacy inbound casts
  117. // without checking, panicking the whole process.
  118. func TestBuildUserAccountShadowsocksUnknownCipherErrors(t *testing.T) {
  119. for _, cipher := range []string{"", "rc4-md5", "2022-blake3-future-gcm", "none"} {
  120. t.Run("cipher="+cipher, func(t *testing.T) {
  121. user := map[string]any{"email": "[email protected]", "password": "pw"}
  122. if cipher != "" {
  123. user["cipher"] = cipher
  124. }
  125. account, err := buildUserAccount("shadowsocks", user)
  126. if err == nil {
  127. t.Fatalf("cipher %q built account %v, want an error instead of an account-type guess", cipher, account)
  128. }
  129. if !strings.Contains(err.Error(), "unknown cipher") {
  130. t.Fatalf("error = %q, want it to name the unknown cipher", err)
  131. }
  132. })
  133. }
  134. }
  135. func TestBuildUserAccountShadowsocksMissingPassword(t *testing.T) {
  136. user := map[string]any{"email": "[email protected]", "cipher": "aes-256-gcm"}
  137. if _, err := buildUserAccount("shadowsocks", user); err == nil {
  138. t.Fatal("expected an error for a shadowsocks user without a password")
  139. }
  140. }