inbound_amneziawg.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. package service
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "strings"
  7. "gorm.io/gorm"
  8. "github.com/mhsanaei/3x-ui/v3/internal/amneziawg"
  9. "github.com/mhsanaei/3x-ui/v3/internal/amneziawgnet"
  10. "github.com/mhsanaei/3x-ui/v3/internal/database"
  11. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  12. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  13. "github.com/mhsanaei/3x-ui/v3/internal/tuic"
  14. wgutil "github.com/mhsanaei/3x-ui/v3/internal/util/wireguard"
  15. )
  16. // DesiredAmneziaWGInstances derives the AmneziaWG interfaces this panel
  17. // should be running: one instance per enabled local AmneziaWG inbound,
  18. // serving only the peers of clients that are both enabled in the inbound
  19. // settings and not depletion-disabled in client_traffics. That is the same
  20. // effective peer set buildInboundForLocalRuntime pushes on interactive edits,
  21. // so the reconcile job and the push path agree on one fingerprint — see
  22. // DesiredMtprotoInstances, which this mirrors exactly.
  23. func (s *InboundService) DesiredAmneziaWGInstances() ([]amneziawg.Instance, error) {
  24. db := database.GetDB()
  25. var inbounds []*model.Inbound
  26. err := db.Model(model.Inbound{}).
  27. Where("protocol = ? AND enable = ? AND node_id IS NULL", model.AmneziaWG, true).
  28. Find(&inbounds).Error
  29. if err != nil {
  30. return nil, err
  31. }
  32. if len(inbounds) == 0 {
  33. return nil, nil
  34. }
  35. instances := make([]amneziawg.Instance, 0, len(inbounds))
  36. for _, ib := range inbounds {
  37. inst, ok := amneziawg.InstanceFromInbound(ib)
  38. if !ok {
  39. continue
  40. }
  41. instances = append(instances, inst)
  42. }
  43. emails := make([]string, 0)
  44. for _, inst := range instances {
  45. for _, e := range inst.Peers {
  46. emails = append(emails, e.Email)
  47. }
  48. }
  49. disabled, err := trafficDisabledEmails(db, emails)
  50. if err != nil {
  51. return nil, err
  52. }
  53. served := instances[:0]
  54. for _, inst := range instances {
  55. kept := make([]amneziawg.Peer, 0, len(inst.Peers))
  56. for _, e := range inst.Peers {
  57. if _, off := disabled[e.Email]; !off {
  58. kept = append(kept, e)
  59. }
  60. }
  61. inst.Peers = kept
  62. if len(kept) > 0 {
  63. served = append(served, inst)
  64. }
  65. }
  66. return served, nil
  67. }
  68. // applyLocalAmneziaWG pushes a single local AmneziaWG inbound's current peer
  69. // set to its interface right after a client edit commits, so an add,
  70. // removal, re-key or enable-toggle takes effect immediately instead of
  71. // waiting up to 10s for the reconcile job. It re-reads the inbound so it sees
  72. // the committed settings, filters depleted clients exactly like the
  73. // reconcile job, and is a no-op for node-owned or non-AmneziaWG inbounds.
  74. // Failures are logged and swallowed: the reconcile job is the backstop.
  75. // Mirrors applyLocalMtproto.
  76. func (s *InboundService) applyLocalAmneziaWG(inboundId int) {
  77. inbound, err := s.GetInbound(inboundId)
  78. if err != nil || inbound == nil || inbound.Protocol != model.AmneziaWG || inbound.NodeID != nil {
  79. return
  80. }
  81. rt, err := s.runtimeFor(inbound)
  82. if err != nil {
  83. return
  84. }
  85. payload := inbound
  86. if inbound.Enable {
  87. if built, bErr := s.buildInboundForLocalRuntime(database.GetDB(), inbound); bErr == nil {
  88. payload = built
  89. }
  90. }
  91. if err := rt.UpdateInbound(context.Background(), inbound, payload); err != nil {
  92. logger.Debugf("amneziawg: immediate apply failed for inbound %d: %v", inboundId, err)
  93. }
  94. }
  95. // defaultAmneziaWGServer builds a fresh server block: a random AmneziaWG 3.1
  96. // obfuscation set, the default tunnel subnet/DNS, and a freshly generated
  97. // keypair.
  98. func defaultAmneziaWGServer() (*amneziawg.ServerSettings, error) {
  99. obf := amneziawg.GenerateObfuscation31()
  100. server := &amneziawg.ServerSettings{
  101. SubnetIP: "10.8.1.0",
  102. SubnetCIDR: 24,
  103. PrimaryDNS: "8.8.8.8",
  104. SecondaryDNS: "8.8.4.4",
  105. Jc: obf.Jc,
  106. Jmin: obf.Jmin,
  107. Jmax: obf.Jmax,
  108. S1: obf.S1,
  109. S2: obf.S2,
  110. S3: obf.S3,
  111. S4: obf.S4,
  112. H1: obf.H1,
  113. H2: obf.H2,
  114. H3: obf.H3,
  115. H4: obf.H4,
  116. I1: obf.I1,
  117. HeaderProtectionKey: obf.HeaderProtectionKey,
  118. ContentPaddingAddition: obf.ContentPaddingAddition,
  119. RekeyAfterTime: obf.RekeyAfterTime,
  120. RekeyTimeout: obf.RekeyTimeout,
  121. RejectAfterTime: obf.RejectAfterTime,
  122. KeepaliveTimeout: obf.KeepaliveTimeout,
  123. MaxHandshakeAttempts: obf.MaxHandshakeAttempts,
  124. RandomTrailers: obf.RandomTrailers,
  125. DisableCookies: obf.DisableCookies,
  126. }
  127. if err := fillAmneziaWGServerKeys(server); err != nil {
  128. return nil, err
  129. }
  130. return server, nil
  131. }
  132. // fillAmneziaWGServerKeys generates a real WireGuard-compatible keypair for
  133. // the server block when one is missing.
  134. func fillAmneziaWGServerKeys(server *amneziawg.ServerSettings) error {
  135. priv, pub, err := wgutil.GenerateWireguardKeypair()
  136. if err != nil {
  137. return fmt.Errorf("amneziawg: generate server keypair: %w", err)
  138. }
  139. server.PrivateKey = priv
  140. server.PublicKey = pub
  141. return nil
  142. }
  143. // resolveAmneziaWGServerKeys settles the server keypair for a save. An omitted
  144. // key means "unchanged", never "mint a new one": rotating it silently
  145. // invalidates every client config already handed out.
  146. func resolveAmneziaWGServerKeys(server *amneziawg.ServerSettings, oldSettings string) error {
  147. if server.PrivateKey == "" {
  148. storedPriv, storedPub := storedAmneziaWGServerKeys(oldSettings)
  149. if storedPriv == "" {
  150. return fillAmneziaWGServerKeys(server)
  151. }
  152. server.PrivateKey, server.PublicKey = storedPriv, storedPub
  153. }
  154. if server.PublicKey == "" {
  155. pub, err := wgutil.PublicKeyFromPrivate(server.PrivateKey)
  156. if err != nil {
  157. return fmt.Errorf("amneziawg: derive server public key: %w", err)
  158. }
  159. server.PublicKey = pub
  160. }
  161. return nil
  162. }
  163. // storedAmneziaWGServerKeys returns the keypair already saved for this inbound.
  164. // oldSettings is empty on a first save, and need not be valid AmneziaWG JSON.
  165. func storedAmneziaWGServerKeys(oldSettings string) (priv, pub string) {
  166. if strings.TrimSpace(oldSettings) == "" {
  167. return "", ""
  168. }
  169. var prev amneziawg.InboundSettings
  170. if err := json.Unmarshal([]byte(oldSettings), &prev); err != nil || prev.Server == nil {
  171. return "", ""
  172. }
  173. return prev.Server.PrivateKey, prev.Server.PublicKey
  174. }
  175. // normalizeAmneziaWGSettings ensures an AmneziaWG inbound's settings have a
  176. // valid server block, generating one (fresh obfuscation params + keypair) on
  177. // first save and validating a manually-edited one so a bad entry can't bring
  178. // the interface down on the next apply. A no-op for every other protocol.
  179. func (s *InboundService) normalizeAmneziaWGSettings(inbound *model.Inbound, oldSettings string) error {
  180. if inbound.Protocol != model.AmneziaWG {
  181. return nil
  182. }
  183. trimmed := strings.TrimSpace(inbound.Settings)
  184. if trimmed == "" || trimmed == "null" || trimmed == "{}" {
  185. server, err := defaultAmneziaWGServer()
  186. if err != nil {
  187. return err
  188. }
  189. settings := amneziawg.InboundSettings{Server: server, Clients: []model.Client{}}
  190. bs, err := json.MarshalIndent(settings, "", " ")
  191. if err != nil {
  192. return err
  193. }
  194. inbound.Settings = string(bs)
  195. return nil
  196. }
  197. var parsed amneziawg.InboundSettings
  198. if err := json.Unmarshal([]byte(inbound.Settings), &parsed); err != nil {
  199. return fmt.Errorf("amneziawg: invalid settings: %w", err)
  200. }
  201. if parsed.Server == nil {
  202. server, err := defaultAmneziaWGServer()
  203. if err != nil {
  204. return err
  205. }
  206. parsed.Server = server
  207. } else if err := resolveAmneziaWGServerKeys(parsed.Server, oldSettings); err != nil {
  208. return err
  209. }
  210. parsed.Server.HeaderProtectionKey = strings.TrimSpace(parsed.Server.HeaderProtectionKey)
  211. for _, f := range []*string{
  212. &parsed.Server.ContentPaddingAddition, &parsed.Server.RekeyAfterTime,
  213. &parsed.Server.RekeyTimeout, &parsed.Server.RejectAfterTime,
  214. &parsed.Server.KeepaliveTimeout, &parsed.Server.MaxHandshakeAttempts,
  215. } {
  216. *f = amneziawg.CanonicalizeUintRange(*f)
  217. }
  218. if err := amneziawg.ValidateServerObfuscation(parsed.Server.Obfuscation()); err != nil {
  219. return fmt.Errorf("amneziawg: %w", err)
  220. }
  221. if err := amneziawg.ValidateIPv6Subnet(parsed.Server.IPv6Enabled, parsed.Server.IPv6Subnet); err != nil {
  222. return fmt.Errorf("amneziawg: %w", err)
  223. }
  224. if err := amneziawg.ValidateSubnetIPv4(parsed.Server.SubnetIP, parsed.Server.SubnetCIDR); err != nil {
  225. return fmt.Errorf("amneziawg: %w", err)
  226. }
  227. if err := amneziawg.ValidateInterfaceName(parsed.Server.ExternalInterface); err != nil {
  228. return fmt.Errorf("amneziawg: externalInterface: %w", err)
  229. }
  230. if err := amneziawg.ValidateInterfaceName(parsed.Server.IPv6ExternalInterface); err != nil {
  231. return fmt.Errorf("amneziawg: ipv6ExternalInterface: %w", err)
  232. }
  233. if err := amneziawg.ValidateConfigValue("privateKey", parsed.Server.PrivateKey); err != nil {
  234. return fmt.Errorf("amneziawg: %w", err)
  235. }
  236. if err := amneziawg.ValidateConfigValue("publicKey", parsed.Server.PublicKey); err != nil {
  237. return fmt.Errorf("amneziawg: %w", err)
  238. }
  239. signaturePackets := []struct{ field, v string }{
  240. {"i1", parsed.Server.I1},
  241. {"i2", parsed.Server.I2},
  242. {"i3", parsed.Server.I3},
  243. {"i4", parsed.Server.I4},
  244. {"i5", parsed.Server.I5},
  245. }
  246. for _, sp := range signaturePackets {
  247. if err := amneziawg.ValidateConfigValue(sp.field, sp.v); err != nil {
  248. return fmt.Errorf("amneziawg: %w", err)
  249. }
  250. }
  251. portCtx, err := s.loadPortConflictContext(database.GetDB(), inbound.NodeID)
  252. if err != nil {
  253. return err
  254. }
  255. for i := range parsed.Clients {
  256. c := &parsed.Clients[i]
  257. if err := s.amneziaWGForwardedPortsConflict(portCtx, c); err != nil {
  258. return err
  259. }
  260. if err := amneziawg.ValidateConfigValue("email", c.Email); err != nil {
  261. return fmt.Errorf("amneziawg: %w", err)
  262. }
  263. if err := amneziawg.ValidateConfigValue("publicKey", c.PublicKey); err != nil {
  264. return fmt.Errorf("amneziawg: client %q: %w", c.Email, err)
  265. }
  266. if err := amneziawg.ValidateConfigValue("preSharedKey", c.PreSharedKey); err != nil {
  267. return fmt.Errorf("amneziawg: client %q: %w", c.Email, err)
  268. }
  269. // AllowedIPs lands verbatim in a rendered [Peer] block, so a newline here
  270. // re-opens an [Interface] section whose PostUp runs as root once the
  271. // downloaded config is applied (client app, or awg-quick directly).
  272. normalized, err := normalizeWireguardAllowedIPs(c.AllowedIPs)
  273. if err != nil {
  274. return fmt.Errorf("amneziawg: client %q: %w", c.Email, err)
  275. }
  276. // An enabled peer with no address is skipped by InstanceFromInbound, and
  277. // if it was the only one the whole inbound never starts, silently.
  278. if c.Enable && len(normalized) == 0 {
  279. return fmt.Errorf("amneziawg: client %q: allowedIPs is required", c.Email)
  280. }
  281. c.AllowedIPs = normalized
  282. }
  283. bs, err := json.MarshalIndent(parsed, "", " ")
  284. if err != nil {
  285. return err
  286. }
  287. inbound.Settings = string(bs)
  288. return nil
  289. }
  290. // portConflictContext caches what checkForwardedPortsConflict needs about the
  291. // host a forward listener binds on, so one save costs one query, not N.
  292. type portConflictContext struct {
  293. webPort int
  294. inbounds []*model.Inbound
  295. // onNode: the host is a node, whose web port and relay ports (derived from
  296. // its own inbound ids) this panel does not know; the node re-checks both.
  297. onNode bool
  298. }
  299. // loadPortConflictContext loads every enabled inbound hosted where nodeID's rows
  300. // run -- this panel for nil, else that node -- plus this panel's own port.
  301. func (s *InboundService) loadPortConflictContext(db *gorm.DB, nodeID *int) (portConflictContext, error) {
  302. var ctx portConflictContext
  303. q := db.Model(model.Inbound{}).Where("enable = ?", true)
  304. if nodeID != nil {
  305. ctx.onNode = true
  306. q = q.Where("node_id = ?", *nodeID)
  307. } else {
  308. if webPort, err := (&SettingService{}).GetPort(); err == nil {
  309. ctx.webPort = webPort
  310. }
  311. q = q.Where("node_id IS NULL")
  312. }
  313. err := q.Find(&ctx.inbounds).Error
  314. return ctx, err
  315. }
  316. // amneziaWGForwardedPortsConflict renders one client's ForwardedPorts collision,
  317. // or nil: the single copy both the pre-Save pass and the post-Save re-run use.
  318. func (s *InboundService) amneziaWGForwardedPortsConflict(ctx portConflictContext, c *model.Client) error {
  319. hit := s.checkForwardedPortsConflict(ctx, c.ForwardedPorts)
  320. if hit == "" {
  321. return nil
  322. }
  323. return fmt.Errorf("amneziawg: client %q forwardedPorts collides with %s", c.Email, hit)
  324. }
  325. // checkAmneziaWGForwardedPorts re-runs the guard over one row's stored clients:
  326. // on create it ran before Save, when the row's own ports were not in the context.
  327. func (s *InboundService) checkAmneziaWGForwardedPorts(db *gorm.DB, settings string) error {
  328. var parsed amneziawg.InboundSettings
  329. if err := json.Unmarshal([]byte(settings), &parsed); err != nil {
  330. return nil
  331. }
  332. ctx, err := s.loadPortConflictContext(db, nil)
  333. if err != nil {
  334. return err
  335. }
  336. for i := range parsed.Clients {
  337. if err := s.amneziaWGForwardedPortsConflict(ctx, &parsed.Clients[i]); err != nil {
  338. return err
  339. }
  340. }
  341. return nil
  342. }
  343. // checkForwardedPortsConflict names the panel, inbound or AmneziaWG relay port a
  344. // client's ForwardedPorts spec would collide with: a lost bind race kills the relay.
  345. func (s *InboundService) checkForwardedPortsConflict(ctx portConflictContext, forwardedPorts string) string {
  346. if forwardedPorts == "" {
  347. return ""
  348. }
  349. if amneziawg.ExceedsForwardedPortsCap(forwardedPorts) {
  350. return fmt.Sprintf("more than %d forwarded ports", amneziawg.MaxForwardedPorts)
  351. }
  352. if ctx.webPort > 0 && amneziawg.ForwardedPortsInclude(forwardedPorts, ctx.webPort) {
  353. return fmt.Sprintf("the panel's own port (%d)", ctx.webPort)
  354. }
  355. for _, ib := range ctx.inbounds {
  356. if amneziawg.ForwardedPortsInclude(forwardedPorts, ib.Port) {
  357. name := ib.Remark
  358. if name == "" {
  359. name = ib.Tag
  360. }
  361. return fmt.Sprintf("inbound '%s' (#%d, port %d)", name, ib.Id, ib.Port)
  362. }
  363. if ctx.onNode {
  364. continue
  365. }
  366. var socksPort int
  367. switch ib.Protocol {
  368. case model.AmneziaWG:
  369. socksPort = amneziawgnet.SOCKSPortForInbound(ib.Id)
  370. case model.TUIC:
  371. socksPort = tuic.SOCKSPortForInbound(ib.Id)
  372. default:
  373. continue
  374. }
  375. if amneziawg.ForwardedPortsInclude(forwardedPorts, socksPort) {
  376. name := ib.Remark
  377. if name == "" {
  378. name = ib.Tag
  379. }
  380. return fmt.Sprintf("inbound '%s' (#%d)'s own SOCKS5 relay port (%d)", name, ib.Id, socksPort)
  381. }
  382. }
  383. return ""
  384. }
  385. // GetAmneziaWGDiagnostics returns a live diagnostics snapshot for inbound
  386. // id: interface up/down, listen port, and per-client handshake/traffic
  387. // state, read entirely from data amneziawgnet.Manager already tracks --
  388. // gathering it can never itself change anything. Returns an error only
  389. // when id doesn't name an AmneziaWG inbound at all; an inbound that simply
  390. // isn't running right now (disabled, no enabled clients, or reconcile
  391. // hasn't caught up yet) comes back as amneziawgnet.Diagnostics{}
  392. // (Running=false), not an error, since that's a normal state an admin
  393. // might specifically be checking for.
  394. func (s *InboundService) GetAmneziaWGDiagnostics(id int) (amneziawgnet.Diagnostics, error) {
  395. inbound, err := s.GetInbound(id)
  396. if err != nil {
  397. return amneziawgnet.Diagnostics{}, err
  398. }
  399. if inbound.Protocol != model.AmneziaWG {
  400. return amneziawgnet.Diagnostics{}, fmt.Errorf("inbound %d is not an AmneziaWG inbound", id)
  401. }
  402. inst, ok := amneziawg.InstanceFromInbound(inbound)
  403. if !ok {
  404. return amneziawgnet.Diagnostics{}, nil
  405. }
  406. return amneziawgnet.Diagnose(inst.Id, inst.Peers), nil
  407. }