1
0

service_wireguard_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. package sub
  2. import (
  3. "fmt"
  4. "net/url"
  5. "strings"
  6. "testing"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database"
  8. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  9. wgutil "github.com/mhsanaei/3x-ui/v3/internal/util/wireguard"
  10. )
  11. func TestGenWireguardLinkFields(t *testing.T) {
  12. serverPriv, serverPub, err := wgutil.GenerateWireguardKeypair()
  13. if err != nil {
  14. t.Fatalf("keypair: %v", err)
  15. }
  16. clientPriv, _, err := wgutil.GenerateWireguardKeypair()
  17. if err != nil {
  18. t.Fatalf("client keypair: %v", err)
  19. }
  20. inbound := &model.Inbound{
  21. Listen: "203.0.113.7",
  22. Port: 51820,
  23. Protocol: model.WireGuard,
  24. Remark: "wg-sub",
  25. Settings: `{"secretKey":"` + serverPriv + `","mtu":1420,"clients":[{"email":"user","privateKey":"` + clientPriv + `","allowedIPs":["10.0.0.2/32"],"keepAlive":25}]}`,
  26. }
  27. s := &SubService{}
  28. link := s.genWireguardLink(inbound, "user")
  29. u, err := url.Parse(link)
  30. if err != nil {
  31. t.Fatalf("link does not parse: %v\n got: %s", err, link)
  32. }
  33. if u.Scheme != "wireguard" {
  34. t.Fatalf("scheme = %q, want wireguard", u.Scheme)
  35. }
  36. if u.Host != "203.0.113.7:51820" {
  37. t.Fatalf("host = %q, want 203.0.113.7:51820", u.Host)
  38. }
  39. if u.User.Username() != clientPriv {
  40. t.Fatalf("userinfo = %q, want client private key %q", u.User.Username(), clientPriv)
  41. }
  42. q := u.Query()
  43. if q.Get("publickey") != serverPub {
  44. t.Fatalf("publickey = %q, want server public key %q", q.Get("publickey"), serverPub)
  45. }
  46. if q.Get("address") != "10.0.0.2/32" {
  47. t.Fatalf("address = %q, want 10.0.0.2/32", q.Get("address"))
  48. }
  49. if q.Get("mtu") != "1420" {
  50. t.Fatalf("mtu = %q, want 1420", q.Get("mtu"))
  51. }
  52. }
  53. func TestGenWireguardLinkMultiAllowedIPs(t *testing.T) {
  54. serverPriv, _, err := wgutil.GenerateWireguardKeypair()
  55. if err != nil {
  56. t.Fatalf("keypair: %v", err)
  57. }
  58. clientPriv, _, err := wgutil.GenerateWireguardKeypair()
  59. if err != nil {
  60. t.Fatalf("client keypair: %v", err)
  61. }
  62. inbound := &model.Inbound{
  63. Listen: "203.0.113.7",
  64. Port: 51820,
  65. Protocol: model.WireGuard,
  66. Remark: "wg-sub",
  67. Settings: `{"secretKey":"` + serverPriv + `","clients":[{"email":"user","privateKey":"` + clientPriv + `","allowedIPs":["10.0.0.2/32","fd00::2/128"]}]}`,
  68. }
  69. s := &SubService{}
  70. link := s.genWireguardLink(inbound, "user")
  71. u, err := url.Parse(link)
  72. if err != nil {
  73. t.Fatalf("link does not parse: %v\n got: %s", err, link)
  74. }
  75. if got, want := u.Query().Get("address"), "10.0.0.2/32,fd00::2/128"; got != want {
  76. t.Fatalf("address = %q, want %q (all allowed IPs joined, not just the first)", got, want)
  77. }
  78. }
  79. func TestGenWireguardLinkWrongProtocol(t *testing.T) {
  80. s := &SubService{}
  81. vless := &model.Inbound{Protocol: model.VLESS, Settings: `{"clients":[{"email":"user"}]}`}
  82. if got := s.genWireguardLink(vless, "user"); got != "" {
  83. t.Fatalf("wrong protocol should yield empty link, got %q", got)
  84. }
  85. }
  86. func TestGenWireguardLinkNoKey(t *testing.T) {
  87. s := &SubService{}
  88. inbound := &model.Inbound{
  89. Protocol: model.WireGuard,
  90. Port: 51820,
  91. Settings: `{"secretKey":"x","clients":[{"email":"user"}]}`,
  92. }
  93. if got := s.genWireguardLink(inbound, "user"); got != "" {
  94. t.Fatalf("client without private key should yield empty link, got %q", got)
  95. }
  96. }
  97. func TestGetInboundsBySubIdIncludesWireguard(t *testing.T) {
  98. initSubDB(t)
  99. db := database.GetDB()
  100. in := &model.Inbound{Port: 51820, Protocol: model.WireGuard, Enable: true, Tag: "wg-sub", Settings: `{"secretKey":"x","clients":[]}`}
  101. if err := db.Create(in).Error; err != nil {
  102. t.Fatalf("create inbound: %v", err)
  103. }
  104. rec := &model.ClientRecord{Email: "u@wg", SubID: "subwg", Enable: true}
  105. if err := db.Create(rec).Error; err != nil {
  106. t.Fatalf("create client: %v", err)
  107. }
  108. if err := db.Create(&model.ClientInbound{ClientId: rec.Id, InboundId: in.Id}).Error; err != nil {
  109. t.Fatalf("create link: %v", err)
  110. }
  111. s := &SubService{}
  112. inbounds, err := s.getInboundsBySubId("subwg")
  113. if err != nil {
  114. t.Fatalf("getInboundsBySubId: %v", err)
  115. }
  116. if len(inbounds) != 1 || inbounds[0].Id != in.Id {
  117. t.Fatalf("wireguard inbound not returned for subId: %+v", inbounds)
  118. }
  119. }
  120. func mustWireguardKeypair(t *testing.T) (string, string) {
  121. t.Helper()
  122. priv, pub, err := wgutil.GenerateWireguardKeypair()
  123. if err != nil {
  124. t.Fatalf("keypair: %v", err)
  125. }
  126. return priv, pub
  127. }
  128. func parseWireguardSubLink(t *testing.T, link string) *url.URL {
  129. t.Helper()
  130. u, err := url.Parse(link)
  131. if err != nil {
  132. t.Fatalf("parse wireguard link: %v\n got: %s", err, link)
  133. }
  134. if u.Scheme != "wireguard" {
  135. t.Fatalf("scheme = %q, want wireguard (%s)", u.Scheme, link)
  136. }
  137. return u
  138. }
  139. // The shared clients row holds the last sync's tunnel identity. Each wireguard://
  140. // entry must keep its own key and both IPv4 and IPv6 addresses, in either sort order (#6641).
  141. func TestGetSubs_PreservesPerInboundWireGuardIdentity(t *testing.T) {
  142. serverAPriv, serverAPub := mustWireguardKeypair(t)
  143. serverBPriv, serverBPub := mustWireguardKeypair(t)
  144. privA, _ := mustWireguardKeypair(t)
  145. privB, _ := mustWireguardKeypair(t)
  146. mergedPriv, _ := mustWireguardKeypair(t)
  147. const (
  148. email = "dual@wg"
  149. subID = "sub-wg-identity"
  150. mergedAddr = "10.9.9.9/32,fd00:9::9/128"
  151. )
  152. nodes := []struct {
  153. tag, listen, priv, serverPriv, serverPub string
  154. port int
  155. allowed []string
  156. }{
  157. {"wg-a", "203.0.113.10", privA, serverAPriv, serverAPub, 51820, []string{"10.1.0.2/32", "fd00:1::2/128"}},
  158. {"wg-b", "203.0.113.11", privB, serverBPriv, serverBPub, 51821, []string{"10.2.0.2/32", "fd00:2::2/128"}},
  159. }
  160. for _, tc := range []struct {
  161. name string
  162. sort [2]int
  163. order [2]int
  164. }{
  165. {name: "creation order", sort: [2]int{1, 2}, order: [2]int{0, 1}},
  166. {name: "reversed subscription sort", sort: [2]int{2, 1}, order: [2]int{1, 0}},
  167. } {
  168. t.Run(tc.name, func(t *testing.T) {
  169. initSubDB(t)
  170. db := database.GetDB()
  171. inbounds := make([]*model.Inbound, len(nodes))
  172. for i, n := range nodes {
  173. settings := fmt.Sprintf(
  174. `{"secretKey":%q,"mtu":1420,"clients":[{"email":%q,"privateKey":%q,"allowedIPs":[%q,%q],"enable":true}]}`,
  175. n.serverPriv, email, n.priv, n.allowed[0], n.allowed[1],
  176. )
  177. ib := &model.Inbound{
  178. UserId: 1, Tag: n.tag, Enable: true, Listen: n.listen, Port: n.port,
  179. Protocol: model.WireGuard, Remark: n.tag, Settings: settings, SubSortIndex: tc.sort[i],
  180. }
  181. if err := db.Create(ib).Error; err != nil {
  182. t.Fatalf("create %s: %v", n.tag, err)
  183. }
  184. inbounds[i] = ib
  185. }
  186. rec := &model.ClientRecord{
  187. Email: email, SubID: subID, Enable: true,
  188. PrivateKey: mergedPriv, AllowedIPs: mergedAddr,
  189. PreSharedKey: "sharedpsk", KeepAlive: 25,
  190. }
  191. if err := db.Create(rec).Error; err != nil {
  192. t.Fatalf("create client: %v", err)
  193. }
  194. for _, ib := range inbounds {
  195. if err := db.Create(&model.ClientInbound{ClientId: rec.Id, InboundId: ib.Id}).Error; err != nil {
  196. t.Fatalf("link %s: %v", ib.Tag, err)
  197. }
  198. }
  199. links, _, _, _, err := NewSubService("").GetSubs(subID, "sub.example.com")
  200. if err != nil {
  201. t.Fatalf("GetSubs: %v", err)
  202. }
  203. if len(links) != len(nodes) {
  204. t.Fatalf("links = %d, want %d: %v", len(links), len(nodes), links)
  205. }
  206. for outIdx, nodeIdx := range tc.order {
  207. n := nodes[nodeIdx]
  208. other := nodes[1-nodeIdx]
  209. u := parseWireguardSubLink(t, links[outIdx])
  210. if u.Host != fmt.Sprintf("%s:%d", n.listen, n.port) {
  211. t.Fatalf("host = %q, want %s:%d", u.Host, n.listen, n.port)
  212. }
  213. if u.User.Username() != n.priv {
  214. t.Fatalf("private key = %q, want inbound key %q", u.User.Username(), n.priv)
  215. }
  216. q := u.Query()
  217. if got, want := q.Get("address"), strings.Join(n.allowed, ","); got != want {
  218. t.Fatalf("address = %q, want %q", got, want)
  219. }
  220. if q.Get("publickey") != n.serverPub {
  221. t.Fatalf("publickey = %q, want %q", q.Get("publickey"), n.serverPub)
  222. }
  223. if q.Get("presharedkey") != "" || q.Get("keepalive") != "" {
  224. t.Fatalf("optional fields inherited shared values: %s", u.RawQuery)
  225. }
  226. if u.User.Username() == mergedPriv || strings.Contains(q.Get("address"), "10.9.9.9") || strings.Contains(q.Get("address"), other.allowed[0]) || strings.Contains(q.Get("address"), other.allowed[1]) {
  227. t.Fatalf("link borrowed another tunnel identity: %s", links[outIdx])
  228. }
  229. }
  230. })
  231. }
  232. }
  233. // A peer missing from settings, or settings that do not parse, must not emit the
  234. // shared clients.wg_* identity. A sibling inbound with its own peer still does (#6641).
  235. func TestGetSubs_WireGuardUnavailableSettingsEmitNoSharedConfig(t *testing.T) {
  236. initSubDB(t)
  237. db := database.GetDB()
  238. serverPriv, serverPub := mustWireguardKeypair(t)
  239. validPriv, _ := mustWireguardKeypair(t)
  240. otherPriv, _ := mustWireguardKeypair(t)
  241. mergedPriv, _ := mustWireguardKeypair(t)
  242. const (
  243. email = "dual@wg"
  244. subID = "sub-wg-missing"
  245. mergedAddr = "10.9.9.9/32,fd00:9::9/128"
  246. )
  247. validAllowed := []string{"10.4.0.2/32", "fd00:4::2/128"}
  248. validSettings := fmt.Sprintf(
  249. `{"secretKey":%q,"clients":[{"email":%q,"privateKey":%q,"allowedIPs":[%q,%q],"enable":true}]}`,
  250. serverPriv, email, validPriv, validAllowed[0], validAllowed[1],
  251. )
  252. absentSettings := fmt.Sprintf(
  253. `{"secretKey":%q,"clients":[{"email":"someone-else@wg","privateKey":%q,"allowedIPs":["10.8.9.9/32"],"enable":true}]}`,
  254. serverPriv, otherPriv,
  255. )
  256. specs := []struct {
  257. tag, listen, settings string
  258. port int
  259. }{
  260. {"wg-bad-json", "203.0.113.31", `{not-json`, 51831},
  261. {"wg-absent-peer", "203.0.113.32", absentSettings, 51832},
  262. {"wg-valid", "203.0.113.33", validSettings, 51833},
  263. }
  264. inbounds := make([]*model.Inbound, len(specs))
  265. for i, sp := range specs {
  266. ib := &model.Inbound{
  267. UserId: 1, Tag: sp.tag, Enable: true, Listen: sp.listen, Port: sp.port,
  268. Protocol: model.WireGuard, Remark: sp.tag, Settings: sp.settings, SubSortIndex: i + 1,
  269. }
  270. if err := db.Create(ib).Error; err != nil {
  271. t.Fatalf("create %s: %v", sp.tag, err)
  272. }
  273. inbounds[i] = ib
  274. }
  275. rec := &model.ClientRecord{
  276. Email: email, SubID: subID, Enable: true,
  277. PrivateKey: mergedPriv, AllowedIPs: mergedAddr,
  278. PreSharedKey: "sharedpsk", KeepAlive: 25,
  279. }
  280. if err := db.Create(rec).Error; err != nil {
  281. t.Fatalf("create client: %v", err)
  282. }
  283. for _, ib := range inbounds {
  284. if err := db.Create(&model.ClientInbound{ClientId: rec.Id, InboundId: ib.Id}).Error; err != nil {
  285. t.Fatalf("link %s: %v", ib.Tag, err)
  286. }
  287. }
  288. links, _, _, _, err := NewSubService("").GetSubs(subID, "sub.example.com")
  289. if err != nil {
  290. t.Fatalf("GetSubs: %v", err)
  291. }
  292. if len(links) != 1 {
  293. t.Fatalf("links = %d, want 1 (absent and malformed inbounds must not emit the shared row): %q", len(links), links)
  294. }
  295. u := parseWireguardSubLink(t, links[0])
  296. if u.Host != "203.0.113.33:51833" {
  297. t.Fatalf("host = %q, want the valid inbound", u.Host)
  298. }
  299. if u.User.Username() != validPriv {
  300. t.Fatalf("private key = %q, want inbound key", u.User.Username())
  301. }
  302. if got, want := u.Query().Get("address"), strings.Join(validAllowed, ","); got != want {
  303. t.Fatalf("address = %q, want %q", got, want)
  304. }
  305. if u.Query().Get("publickey") != serverPub || u.Query().Get("presharedkey") != "" || u.Query().Get("keepalive") != "" {
  306. t.Fatalf("query borrowed shared or foreign tunnel fields: %s", u.RawQuery)
  307. }
  308. }
  309. // Explicit empty preshared key and keepalive must not inherit the shared row (#6641).
  310. func TestGetSubs_WireGuardEmptyOptionalTunnelFieldsDoNotInheritShared(t *testing.T) {
  311. initSubDB(t)
  312. db := database.GetDB()
  313. serverPriv, serverPub := mustWireguardKeypair(t)
  314. clientPriv, _ := mustWireguardKeypair(t)
  315. mergedPriv, _ := mustWireguardKeypair(t)
  316. const (
  317. email = "optional@wg"
  318. subID = "sub-wg-optional"
  319. )
  320. allowed := []string{"10.5.0.2/32", "fd00:5::2/128"}
  321. settings := fmt.Sprintf(
  322. `{"secretKey":%q,"clients":[{"email":%q,"privateKey":%q,"allowedIPs":[%q,%q],"preSharedKey":"","keepAlive":0,"enable":true}]}`,
  323. serverPriv, email, clientPriv, allowed[0], allowed[1],
  324. )
  325. ib := &model.Inbound{
  326. UserId: 1, Tag: "wg-optional", Enable: true, Listen: "203.0.113.40", Port: 51840,
  327. Protocol: model.WireGuard, Remark: "wg-optional", Settings: settings,
  328. }
  329. if err := db.Create(ib).Error; err != nil {
  330. t.Fatalf("create inbound: %v", err)
  331. }
  332. rec := &model.ClientRecord{
  333. Email: email, SubID: subID, Enable: true,
  334. PrivateKey: mergedPriv, AllowedIPs: "10.9.9.9/32,fd00:9::9/128",
  335. PreSharedKey: "sharedpsk", KeepAlive: 25,
  336. }
  337. if err := db.Create(rec).Error; err != nil {
  338. t.Fatalf("create client: %v", err)
  339. }
  340. if err := db.Create(&model.ClientInbound{ClientId: rec.Id, InboundId: ib.Id}).Error; err != nil {
  341. t.Fatalf("link client: %v", err)
  342. }
  343. links, _, _, _, err := NewSubService("").GetSubs(subID, "sub.example.com")
  344. if err != nil {
  345. t.Fatalf("GetSubs: %v", err)
  346. }
  347. if len(links) != 1 {
  348. t.Fatalf("links = %d, want 1: %q", len(links), links)
  349. }
  350. u := parseWireguardSubLink(t, links[0])
  351. if u.User.Username() != clientPriv {
  352. t.Fatalf("private key = %q, want inbound key", u.User.Username())
  353. }
  354. if got, want := u.Query().Get("address"), strings.Join(allowed, ","); got != want {
  355. t.Fatalf("address = %q, want %q", got, want)
  356. }
  357. if u.Query().Get("publickey") != serverPub {
  358. t.Fatalf("publickey = %q, want %q", u.Query().Get("publickey"), serverPub)
  359. }
  360. if u.Query().Get("presharedkey") != "" || u.Query().Get("keepalive") != "" || strings.Contains(u.RawQuery, "sharedpsk") || strings.Contains(u.Query().Get("address"), "10.9.9.9") || strings.Contains(u.Query().Get("address"), "fd00:9::9") {
  361. t.Fatalf("link inherited shared tunnel fields: %s", links[0])
  362. }
  363. }