service.go 82 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651
  1. package sub
  2. import (
  3. "crypto/sha256"
  4. "encoding/base64"
  5. "encoding/hex"
  6. "fmt"
  7. "maps"
  8. "net"
  9. "net/url"
  10. "slices"
  11. "strconv"
  12. "strings"
  13. "time"
  14. "github.com/gin-gonic/gin"
  15. "github.com/goccy/go-json"
  16. "github.com/mhsanaei/3x-ui/v3/internal/database"
  17. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  18. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  19. "github.com/mhsanaei/3x-ui/v3/internal/util/common"
  20. "github.com/mhsanaei/3x-ui/v3/internal/util/random"
  21. wgutil "github.com/mhsanaei/3x-ui/v3/internal/util/wireguard"
  22. "github.com/mhsanaei/3x-ui/v3/internal/web/service"
  23. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  24. )
  25. // SubService provides business logic for generating subscription links and managing subscription data.
  26. type SubService struct {
  27. address string
  28. remarkTemplate string
  29. datepicker string
  30. // subscriptionBody is true only when rendering the actual subscription
  31. // content a client app imports (raw /sub fetch, /json, /clash). The remark
  32. // template's per-client info is emitted there (on the first link); every
  33. // other context — the sub info page, the panel's link/QR displays — renders
  34. // the name-only template, like Remnawave.
  35. subscriptionBody bool
  36. // usageShown tracks, per client email, whether the info part of the template
  37. // has already been emitted this request, so it appears on the first body
  38. // link only. Per-request state; reset in PrepareForRequest.
  39. usageShown map[string]bool
  40. inboundService service.InboundService
  41. settingService service.SettingService
  42. // nodesByID is populated per request from the Node table so
  43. // resolveInboundAddress can return the node's address for any
  44. // inbound whose NodeID is set. Keeps the per-link host derivation
  45. // O(1) instead of O(N) DB hits.
  46. nodesByID map[int]*model.Node
  47. // statsByEmail maps a client email to its traffic row across ALL inbounds
  48. // loaded for the request. client_traffics.email is globally unique, so this
  49. // lets statsForClient resolve usage for a client even on an inbound that
  50. // doesn't own its row (multi-inbound subscriptions). Filled in
  51. // getInboundsBySubId; reset per request in PrepareForRequest.
  52. statsByEmail map[string]xray.ClientTraffic
  53. // clientsByInbound caches clients resolved for this request keyed by
  54. // inbound id then email, so the per-protocol link generators look a client
  55. // up without re-parsing the inbound's settings JSON per link.
  56. // fullyPrimedInbounds marks inbounds whose complete client list is cached
  57. // (a miss there is authoritative). Reset per request in PrepareForRequest.
  58. clientsByInbound map[int]map[string]model.Client
  59. fullyPrimedInbounds map[int]bool
  60. // settingsByInbound caches each inbound's settings decoded once per request
  61. // with the clients array left out; generators read only inbound-level
  62. // fields (encryption, method, version, …) from it.
  63. settingsByInbound map[int]map[string]any
  64. }
  65. // NewSubService creates a new subscription service with the given configuration.
  66. func NewSubService(remarkTemplate string) *SubService {
  67. return &SubService{
  68. remarkTemplate: remarkTemplate,
  69. }
  70. }
  71. // ForRequest returns a shallow copy with request-scoped state populated.
  72. // Subscription controllers share one base SubService, so request-specific
  73. // fields such as address and nodesByID must live on a per-request copy.
  74. func (s *SubService) ForRequest(host string) *SubService {
  75. req := *s
  76. req.PrepareForRequest(host)
  77. return &req
  78. }
  79. // PrepareForRequest sets per-request state (host + nodes map) on this
  80. // SubService instance. HTTP handlers should call ForRequest instead so the
  81. // controller's shared base service is never mutated by concurrent requests.
  82. func (s *SubService) PrepareForRequest(host string) {
  83. if !isRoutableHost(host) {
  84. if d := s.configuredPublicHost(); d != "" {
  85. host = d
  86. } else if isLoopbackHost(host) {
  87. host = "localhost"
  88. }
  89. }
  90. s.address = host
  91. s.usageShown = map[string]bool{}
  92. s.statsByEmail = map[string]xray.ClientTraffic{}
  93. s.clientsByInbound = map[int]map[string]model.Client{}
  94. s.fullyPrimedInbounds = map[int]bool{}
  95. s.settingsByInbound = map[int]map[string]any{}
  96. s.loadNodes()
  97. s.loadRemarkSettings()
  98. }
  99. // primeLinkClients caches clients (first occurrence per email, matching the
  100. // old settings-JSON iteration order) so clientForLink resolves them without a
  101. // parse. complete marks the inbound's whole client list as cached.
  102. func (s *SubService) primeLinkClients(inboundId int, clients []model.Client, complete bool) {
  103. if inboundId <= 0 {
  104. return
  105. }
  106. if s.clientsByInbound == nil {
  107. s.clientsByInbound = map[int]map[string]model.Client{}
  108. }
  109. m := s.clientsByInbound[inboundId]
  110. if m == nil {
  111. m = make(map[string]model.Client, len(clients))
  112. s.clientsByInbound[inboundId] = m
  113. }
  114. for _, c := range clients {
  115. if _, exists := m[c.Email]; !exists {
  116. m[c.Email] = c
  117. }
  118. }
  119. if complete {
  120. if s.fullyPrimedInbounds == nil {
  121. s.fullyPrimedInbounds = map[int]bool{}
  122. }
  123. s.fullyPrimedInbounds[inboundId] = true
  124. }
  125. }
  126. // clientForLink resolves one client of an inbound by email for link
  127. // generation: from the per-request cache when primed, otherwise by parsing
  128. // the settings JSON once and caching every client from it.
  129. func (s *SubService) clientForLink(inbound *model.Inbound, email string) (model.Client, bool) {
  130. if m, ok := s.clientsByInbound[inbound.Id]; ok {
  131. if c, hit := m[email]; hit {
  132. return c, true
  133. }
  134. if s.fullyPrimedInbounds[inbound.Id] {
  135. return model.Client{}, false
  136. }
  137. }
  138. clients, err := s.inboundService.GetClients(inbound)
  139. if err != nil {
  140. return model.Client{}, false
  141. }
  142. s.primeLinkClients(inbound.Id, clients, true)
  143. for i := range clients {
  144. if clients[i].Email == email {
  145. return clients[i], true
  146. }
  147. }
  148. return model.Client{}, false
  149. }
  150. // linkSettings returns the inbound's settings decoded once per request with
  151. // the clients array left out — the link generators read only inbound-level
  152. // fields from it and resolve clients via clientForLink. The shallow
  153. // RawMessage pass skips materializing a huge clients array entirely.
  154. func (s *SubService) linkSettings(inbound *model.Inbound) map[string]any {
  155. if inbound.Id > 0 {
  156. if cached, ok := s.settingsByInbound[inbound.Id]; ok {
  157. return cached
  158. }
  159. }
  160. shallow := map[string]json.RawMessage{}
  161. _ = json.Unmarshal([]byte(inbound.Settings), &shallow)
  162. out := make(map[string]any, len(shallow))
  163. for key, raw := range shallow {
  164. if key == "clients" {
  165. continue
  166. }
  167. var value any
  168. _ = json.Unmarshal(raw, &value)
  169. out[key] = value
  170. }
  171. if inbound.Id > 0 {
  172. if s.settingsByInbound == nil {
  173. s.settingsByInbound = map[int]map[string]any{}
  174. }
  175. s.settingsByInbound[inbound.Id] = out
  176. }
  177. return out
  178. }
  179. // loadRemarkSettings populates the per-request remark formatting state so
  180. // every subscription format — raw, JSON, Clash — renders remarks the same way
  181. // (the date formatter reads datepicker). Loading it only in getSubs left
  182. // JSON/Clash with the zero value.
  183. func (s *SubService) loadRemarkSettings() {
  184. var err error
  185. s.datepicker, err = s.settingService.GetDatepicker()
  186. if err != nil {
  187. s.datepicker = "gregorian"
  188. }
  189. }
  190. func (s *SubService) configuredPublicHost() string {
  191. if d, err := s.settingService.GetSubDomain(); err == nil && d != "" {
  192. return d
  193. }
  194. if d, err := s.settingService.GetWebDomain(); err == nil && d != "" {
  195. return d
  196. }
  197. return ""
  198. }
  199. func isRoutableHost(host string) bool {
  200. if host == "" {
  201. return false
  202. }
  203. if ip := net.ParseIP(strings.Trim(host, "[]")); ip != nil {
  204. return !ip.IsLoopback() && !ip.IsUnspecified()
  205. }
  206. return true
  207. }
  208. func isLoopbackHost(host string) bool {
  209. ip := net.ParseIP(strings.Trim(host, "[]"))
  210. return ip != nil && ip.IsLoopback()
  211. }
  212. // listenIsInternalOnly reports whether a bind address is reachable only from
  213. // the same host — a loopback IP or a unix-domain socket. Such an inbound can't
  214. // be dialed directly by a remote client, so when it is the child side of a
  215. // fallback its share link must be projected through the master. A public or
  216. // wildcard listen (""/0.0.0.0/::) is reachable on its own port and advertises
  217. // itself.
  218. func listenIsInternalOnly(listen string) bool {
  219. if listen == "" {
  220. return false
  221. }
  222. if listen[0] == '@' || listen[0] == '/' {
  223. return true
  224. }
  225. return isLoopbackHost(listen)
  226. }
  227. // matchingClients returns the inbound's clients whose SubID equals subId,
  228. // resolved from the normalized clients/client_inbounds tables (both filter
  229. // columns indexed) instead of parsing the settings JSON — at large client
  230. // counts that parse made every subscription fetch cost seconds. The
  231. // case-insensitive email dedupe stays as cheap insurance even though
  232. // clients.email is unique, preserving the #5134 guarantee that duplicate
  233. // settings entries never fan out into duplicate profiles. Resolved clients
  234. // are primed into the per-request cache so the link generators don't parse
  235. // settings either.
  236. func (s *SubService) matchingClients(inbound *model.Inbound, subId string) []model.Client {
  237. clients, err := s.inboundService.GetClientsBySubId(inbound.Id, subId)
  238. if err != nil {
  239. logger.Error("SubService - GetClientsBySubId: Unable to get clients from inbound")
  240. return nil
  241. }
  242. var out []model.Client
  243. seen := make(map[string]struct{}, len(clients))
  244. for _, client := range clients {
  245. key := strings.ToLower(client.Email)
  246. if _, dup := seen[key]; dup {
  247. continue
  248. }
  249. seen[key] = struct{}{}
  250. out = append(out, client)
  251. }
  252. s.primeLinkClients(inbound.Id, out, false)
  253. return out
  254. }
  255. // GetSubs retrieves subscription links for a given subscription ID and host.
  256. func (s *SubService) GetSubs(subId string, host string) ([]string, []string, int64, xray.ClientTraffic, error) {
  257. return s.ForRequest(host).getSubs(subId)
  258. }
  259. func (s *SubService) getSubs(subId string) ([]string, []string, int64, xray.ClientTraffic, error) {
  260. var result []string
  261. var emails []string
  262. var traffic xray.ClientTraffic
  263. var hasEnabledClient bool
  264. inbounds, err := s.getInboundsBySubId(subId)
  265. if err != nil {
  266. return nil, nil, 0, traffic, err
  267. }
  268. externalLinks, err := s.getClientExternalLinksBySubId(subId)
  269. if err != nil {
  270. return nil, nil, 0, traffic, err
  271. }
  272. if len(inbounds) == 0 && len(externalLinks) == 0 {
  273. return nil, nil, 0, traffic, nil
  274. }
  275. seenEmails := make(map[string]struct{})
  276. for _, inbound := range inbounds {
  277. clients := s.matchingClients(inbound, subId)
  278. if len(clients) == 0 {
  279. continue
  280. }
  281. s.projectThroughFallbackMaster(inbound)
  282. // Host overrides apply AFTER fallback projection so a host's
  283. // address/TLS wins over the projected master stream.
  284. hostEps := s.hostEndpoints(inbound, "raw")
  285. for _, client := range clients {
  286. if client.Enable {
  287. hasEnabledClient = true
  288. }
  289. var link string
  290. if len(hostEps) > 0 {
  291. link = s.linkFromHosts(inbound, client, hostEps)
  292. } else {
  293. link = s.GetLink(inbound, client.Email)
  294. }
  295. result = append(result, link)
  296. emails = append(emails, client.Email)
  297. seenEmails[client.Email] = struct{}{}
  298. }
  299. }
  300. for _, ext := range externalLinks {
  301. if ext.Enable {
  302. hasEnabledClient = true
  303. }
  304. for _, el := range expandEntry(ext) {
  305. if link := applyRemarkToLink(el.Link, el.Name); link != "" {
  306. result = append(result, link)
  307. emails = append(emails, ext.Email)
  308. seenEmails[ext.Email] = struct{}{}
  309. }
  310. }
  311. }
  312. uniqueEmails := make([]string, 0, len(seenEmails))
  313. for e := range seenEmails {
  314. uniqueEmails = append(uniqueEmails, e)
  315. }
  316. traffic, lastOnline := s.AggregateTrafficByEmails(uniqueEmails)
  317. traffic.Enable = hasEnabledClient
  318. return result, emails, lastOnline, traffic, nil
  319. }
  320. // inboundLinks builds the share links for every distinct client of one inbound
  321. // the same way getSubs does — managed Host endpoints win over the plain link so
  322. // {{HOST}} and per-host variants render — but across all clients rather than a
  323. // single subId. Dedups duplicate client JSON entries by email (#5134). Backs the
  324. // panel's "Export all inbound links" so it matches the client/QR pages.
  325. func (s *SubService) inboundLinks(inbound *model.Inbound) []string {
  326. clients, err := s.inboundService.GetClients(inbound)
  327. if err != nil {
  328. return nil
  329. }
  330. s.primeLinkClients(inbound.Id, clients, true)
  331. s.projectThroughFallbackMaster(inbound)
  332. hostEps := s.hostEndpoints(inbound, "raw")
  333. var out []string
  334. seen := make(map[string]struct{}, len(clients))
  335. for _, client := range clients {
  336. key := strings.ToLower(client.Email)
  337. if _, dup := seen[key]; dup {
  338. continue
  339. }
  340. seen[key] = struct{}{}
  341. var link string
  342. if len(hostEps) > 0 {
  343. link = s.linkFromHosts(inbound, client, hostEps)
  344. } else {
  345. link = s.GetLink(inbound, client.Email)
  346. }
  347. out = append(out, splitLinkLines(link)...)
  348. }
  349. return out
  350. }
  351. // AggregateTrafficByEmails resolves traffic for every email in one
  352. // query and folds the rows into a single ClientTraffic + lastOnline.
  353. // xray.ClientTraffic.Email is globally unique, so a multi-inbound
  354. // client's single row is attached to exactly one inbound — iterating
  355. // per-inbound ClientStats would miss it on the others. Used by GetSubs,
  356. // SubClashService.GetClash, and SubJsonService.GetJson to keep the
  357. // sub-info header consistent across all three formats.
  358. func (s *SubService) AggregateTrafficByEmails(emails []string) (xray.ClientTraffic, int64) {
  359. var agg xray.ClientTraffic
  360. var lastOnline int64
  361. if len(emails) == 0 {
  362. return agg, 0
  363. }
  364. db := database.GetDB()
  365. var rows []xray.ClientTraffic
  366. if err := db.
  367. Model(&xray.ClientTraffic{}).
  368. Where("email IN ?", emails).
  369. Find(&rows).Error; err != nil {
  370. logger.Warning("SubService - AggregateTrafficByEmails: load by email:", err)
  371. return agg, 0
  372. }
  373. // total/expiry are configured limits owned by the clients table, not the
  374. // runtime traffic rows. In a multi-node setup the node snapshot can reset
  375. // client_traffics.total/expiry_time to 0, so fall back to the clients
  376. // table to keep the Subscription-Userinfo header in sync with the UI (#4645).
  377. limits := make(map[string][2]int64, len(emails))
  378. var records []model.ClientRecord
  379. if err := db.Model(&model.ClientRecord{}).Where("email IN ?", emails).Find(&records).Error; err != nil {
  380. logger.Warning("SubService - AggregateTrafficByEmails: load client limits:", err)
  381. } else {
  382. for _, r := range records {
  383. limits[r.Email] = [2]int64{r.TotalGB, r.ExpiryTime}
  384. }
  385. }
  386. now := time.Now().UnixMilli()
  387. first := true
  388. for _, ct := range rows {
  389. if ct.LastOnline > lastOnline {
  390. lastOnline = ct.LastOnline
  391. }
  392. total, expiry := ct.Total, ct.ExpiryTime
  393. if lim, ok := limits[ct.Email]; ok {
  394. if total == 0 {
  395. total = lim[0]
  396. }
  397. if expiry == 0 {
  398. expiry = lim[1]
  399. }
  400. }
  401. if first {
  402. agg.Up = ct.Up
  403. agg.Down = ct.Down
  404. agg.Total = total
  405. agg.ExpiryTime = subscriptionExpiryFromClient(now, expiry)
  406. first = false
  407. continue
  408. }
  409. agg.Up += ct.Up
  410. agg.Down += ct.Down
  411. if agg.Total == 0 || total == 0 {
  412. agg.Total = 0
  413. } else {
  414. agg.Total += total
  415. }
  416. normalized := subscriptionExpiryFromClient(now, expiry)
  417. if normalized != agg.ExpiryTime {
  418. agg.ExpiryTime = 0
  419. }
  420. }
  421. return agg, lastOnline
  422. }
  423. func subscriptionExpiryFromClient(nowMs, expiryTime int64) int64 {
  424. if expiryTime > 0 {
  425. return expiryTime
  426. }
  427. if expiryTime < 0 {
  428. return nowMs + (-expiryTime)
  429. }
  430. return 0
  431. }
  432. func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
  433. db := database.GetDB()
  434. var inbounds []*model.Inbound
  435. err := db.Model(model.Inbound{}).Where(`id in (
  436. SELECT DISTINCT inbounds.id
  437. FROM inbounds
  438. JOIN client_inbounds ON client_inbounds.inbound_id = inbounds.id
  439. JOIN clients ON clients.id = client_inbounds.client_id
  440. WHERE
  441. inbounds.protocol in ('vmess','vless','trojan','shadowsocks','hysteria','wireguard','mtproto')
  442. AND clients.sub_id = ? AND inbounds.enable = ?
  443. )`, subId, true).Order("sub_sort_index ASC").Order("id ASC").Find(&inbounds).Error
  444. if err != nil {
  445. return nil, err
  446. }
  447. s.indexStatsBySubId(subId)
  448. return inbounds, nil
  449. }
  450. // indexStatsBySubId loads the traffic rows for just this subscriber's clients
  451. // into statsByEmail so statsForClient can resolve a client's usage on any of
  452. // its inbounds. It replaces preloading every matched inbound's ClientStats,
  453. // which read the entire client_traffics table on every subscription fetch of
  454. // a large inbound; statsForClient's per-email DB fallback covers any miss.
  455. func (s *SubService) indexStatsBySubId(subId string) {
  456. if s.statsByEmail == nil {
  457. s.statsByEmail = map[string]xray.ClientTraffic{}
  458. }
  459. db := database.GetDB()
  460. var emails []string
  461. if err := db.Model(&model.ClientRecord{}).Where("sub_id = ?", subId).Pluck("email", &emails).Error; err != nil {
  462. logger.Error("SubService - indexStatsBySubId: load emails:", err)
  463. return
  464. }
  465. const chunk = 400
  466. for lo := 0; lo < len(emails); lo += chunk {
  467. hi := min(lo+chunk, len(emails))
  468. var rows []xray.ClientTraffic
  469. if err := db.Where("email IN ?", emails[lo:hi]).Find(&rows).Error; err != nil {
  470. logger.Error("SubService - indexStatsBySubId: load traffics:", err)
  471. return
  472. }
  473. for _, st := range rows {
  474. s.statsByEmail[st.Email] = st
  475. }
  476. }
  477. }
  478. // projectThroughFallbackMaster mutates the inbound in place so its
  479. // Listen/Port/StreamSettings reflect the externally reachable master
  480. // when applicable. Covers both fallback mechanisms:
  481. // - panel-tracked: an inbound_fallbacks row where child_id = inbound.Id
  482. // - legacy unix-socket: inbound.Listen begins with "@" and some VLESS/
  483. // Trojan inbound's settings.fallbacks references that listen address
  484. //
  485. // Returns true when a projection happened; sub services call this before
  486. // generating links so a child VLESS-WS bound to 127.0.0.1 emits the
  487. // master's :443 + TLS state instead of its own loopback endpoint.
  488. //
  489. // Projection only applies to a child that is not directly reachable on its
  490. // own listen (loopback or a unix-domain socket). An inbound on a public or
  491. // wildcard listen is reachable on its own port, so it advertises its own
  492. // port + security even when a stale fallback rule still names it as a child —
  493. // otherwise its share link would leak the master's port and Reality/TLS
  494. // settings (#4987).
  495. func (s *SubService) projectThroughFallbackMaster(inbound *model.Inbound) bool {
  496. if inbound == nil {
  497. return false
  498. }
  499. if !listenIsInternalOnly(inbound.Listen) {
  500. return false
  501. }
  502. db := database.GetDB()
  503. var master *model.Inbound
  504. var rule model.InboundFallback
  505. if err := db.Where("child_id = ?", inbound.Id).
  506. Order("sort_order ASC, id ASC").
  507. First(&rule).Error; err == nil {
  508. var m model.Inbound
  509. if err := db.Where("id = ?", rule.MasterId).First(&m).Error; err == nil {
  510. master = &m
  511. }
  512. }
  513. if master == nil && len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
  514. var m model.Inbound
  515. if err := db.Model(model.Inbound{}).
  516. Where("JSON_TYPE(settings, '$.fallbacks') = 'array'").
  517. Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", inbound.Listen).
  518. First(&m).Error; err == nil {
  519. master = &m
  520. }
  521. }
  522. if master == nil {
  523. return false
  524. }
  525. inbound.StreamSettings = mergeStreamFromMaster(inbound.StreamSettings, master.StreamSettings)
  526. inbound.Listen = master.Listen
  527. inbound.Port = master.Port
  528. return true
  529. }
  530. // mergeStreamFromMaster copies the master's security + tlsSettings +
  531. // realitySettings + externalProxy onto the child's stream so the child's
  532. // link advertises the master's TLS / Reality state. Transport (network
  533. // + ws/grpc/etc. settings) stays the child's.
  534. func mergeStreamFromMaster(childStream, masterStream string) string {
  535. var stream map[string]any
  536. _ = json.Unmarshal([]byte(childStream), &stream)
  537. if stream == nil {
  538. stream = map[string]any{}
  539. }
  540. var mst map[string]any
  541. _ = json.Unmarshal([]byte(masterStream), &mst)
  542. if mst == nil {
  543. return childStream
  544. }
  545. stream["security"] = mst["security"]
  546. if v, ok := mst["tlsSettings"]; ok {
  547. stream["tlsSettings"] = v
  548. } else {
  549. delete(stream, "tlsSettings")
  550. }
  551. if v, ok := mst["realitySettings"]; ok {
  552. stream["realitySettings"] = v
  553. } else {
  554. delete(stream, "realitySettings")
  555. }
  556. if v, ok := mst["externalProxy"]; ok {
  557. stream["externalProxy"] = v
  558. }
  559. out, err := json.MarshalIndent(stream, "", " ")
  560. if err != nil {
  561. return childStream
  562. }
  563. return string(out)
  564. }
  565. // GetLink dispatches to the protocol-specific generator for one (inbound, client)
  566. // pair. Returns "" when the inbound's protocol doesn't produce a subscription URL
  567. // (socks, http, mixed, wireguard, dokodemo, tunnel). The returned string may
  568. // contain multiple `\n`-separated URLs when the inbound has externalProxy set.
  569. func (s *SubService) GetLink(inbound *model.Inbound, email string) string {
  570. switch inbound.Protocol {
  571. case "vmess":
  572. return s.genVmessLink(inbound, email)
  573. case "vless":
  574. return s.genVlessLink(inbound, email)
  575. case "trojan":
  576. return s.genTrojanLink(inbound, email)
  577. case "shadowsocks":
  578. return s.genShadowsocksLink(inbound, email)
  579. case "hysteria":
  580. return s.genHysteriaLink(inbound, email)
  581. case "mtproto":
  582. return s.genMtprotoLink(inbound, email)
  583. case "wireguard":
  584. return s.genWireguardLink(inbound, email)
  585. }
  586. return ""
  587. }
  588. // genWireguardLink builds a per-client wireguard:// share link mirroring the
  589. // frontend genWireguardLink: the client's private key is the userinfo, the
  590. // server public key (derived from the inbound secretKey) and the client's
  591. // tunnel address ride in the query. Returns "" when the client has no key.
  592. func (s *SubService) genWireguardLink(inbound *model.Inbound, email string) string {
  593. if inbound.Protocol != model.WireGuard {
  594. return ""
  595. }
  596. settings := s.linkSettings(inbound)
  597. secretKey, _ := settings["secretKey"].(string)
  598. resolved, ok := s.clientForLink(inbound, email)
  599. if !ok || resolved.PrivateKey == "" {
  600. return ""
  601. }
  602. client := &resolved
  603. link := fmt.Sprintf("wireguard://%s@%s", encodeUserinfo(client.PrivateKey), joinHostPort(s.resolveInboundAddress(inbound), inbound.Port))
  604. params := make(map[string]string)
  605. if secretKey != "" {
  606. if pub, err := wgutil.PublicKeyFromPrivate(secretKey); err == nil {
  607. params["publickey"] = pub
  608. }
  609. }
  610. if joined := strings.Join(client.AllowedIPs, ","); joined != "" {
  611. params["address"] = joined
  612. }
  613. if mtu, ok := settings["mtu"].(float64); ok && mtu > 0 {
  614. params["mtu"] = strconv.Itoa(int(mtu))
  615. }
  616. if dns, ok := settings["dns"].(string); ok && dns != "" {
  617. params["dns"] = dns
  618. }
  619. if client.PreSharedKey != "" {
  620. params["presharedkey"] = client.PreSharedKey
  621. }
  622. if client.KeepAlive > 0 {
  623. params["keepalive"] = strconv.Itoa(client.KeepAlive)
  624. }
  625. return buildLinkWithParams(link, params, s.genRemark(inbound, email, "", ""))
  626. }
  627. // genMtprotoLink builds a per-client Telegram proxy deep link for an mtproto
  628. // inbound: the server/port pair plus the client's own FakeTLS secret. The link
  629. // carries no remark fragment — Telegram proxy deep links have no name field, and
  630. // a trailing "#remark" is appended to the last query value by lenient parsers,
  631. // corrupting the server address. The remark is shown separately in the panel UI.
  632. // Returns "" when the client has no secret.
  633. func (s *SubService) genMtprotoLink(inbound *model.Inbound, email string) string {
  634. if inbound.Protocol != model.MTProto {
  635. return ""
  636. }
  637. resolved, ok := s.clientForLink(inbound, email)
  638. if !ok || resolved.Secret == "" {
  639. return ""
  640. }
  641. params := map[string]string{
  642. "server": s.resolveInboundAddress(inbound),
  643. "port": fmt.Sprintf("%d", inbound.Port),
  644. "secret": resolved.Secret,
  645. }
  646. return buildLinkWithParams("tg://proxy", params, "")
  647. }
  648. // Protocol link generators are intentionally ordered as:
  649. // vmess -> vless -> trojan -> shadowsocks -> hysteria.
  650. func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
  651. if inbound.Protocol != model.VMESS {
  652. return ""
  653. }
  654. address := s.resolveInboundAddress(inbound)
  655. obj := map[string]any{
  656. "v": "2",
  657. "add": address,
  658. "port": inbound.Port,
  659. "type": "none",
  660. }
  661. stream := unmarshalStreamSettings(inbound.StreamSettings)
  662. network, _ := stream["network"].(string)
  663. applyVmessNetworkParams(stream, network, obj)
  664. if finalmask, ok := stream["finalmask"].(map[string]any); ok {
  665. applyFinalMaskObj(finalmask, obj)
  666. }
  667. security, _ := stream["security"].(string)
  668. obj["tls"] = security
  669. if security == "tls" {
  670. applyVmessTLSParams(stream, obj)
  671. }
  672. client, ok := s.clientForLink(inbound, email)
  673. if !ok {
  674. return ""
  675. }
  676. obj["id"] = client.ID
  677. obj["scy"] = normalizeVmessSecurity(client.Security)
  678. externalProxies, _ := stream["externalProxy"].([]any)
  679. if len(externalProxies) > 0 {
  680. return s.buildVmessExternalProxyLinks(externalProxies, obj, inbound, email, network)
  681. }
  682. obj["ps"] = s.genRemark(inbound, email, "", network)
  683. return buildVmessLink(obj)
  684. }
  685. // normalizeVmessSecurity maps the vmess security values xray-core v26.7.11
  686. // removed ("none"/"zero"), plus the legacy empty string, to "auto" so links
  687. // and subscriptions stop advertising values the upgraded server rejects on
  688. // the wire.
  689. func normalizeVmessSecurity(security string) string {
  690. switch security {
  691. case "", "none", "zero":
  692. return "auto"
  693. }
  694. return security
  695. }
  696. // vlessEncryptionEnabled reports whether the VLESS inbound settings enable
  697. // VLESS-level encryption (vlessenc / ML-KEM). When on, the encryption/decryption
  698. // fields hold a generated dotted string (e.g. "mlkem768x25519plus.native.0rtt.<key>");
  699. // "none" or empty means off. The value is never the literal "vlessenc" — that is
  700. // the `xray vlessenc` CLI subcommand name, not a stored value.
  701. func vlessEncryptionEnabled(settings map[string]any) bool {
  702. for _, key := range []string{"encryption", "decryption"} {
  703. if v, ok := settings[key].(string); ok && v != "" && v != "none" {
  704. return true
  705. }
  706. }
  707. return false
  708. }
  709. // vlessFlowAllowed reports whether a client's XTLS Vision flow belongs in
  710. // generated links/configs. Mirrors inboundCanEnableTlsFlow in
  711. // internal/web/service: Vision runs on TCP with tls/reality (classic), and on
  712. // XHTTP whenever VLESS encryption (vlessenc / ML-KEM) is enabled — there the
  713. // VLESS-level encryption stands in for the transport TLS that Vision relies
  714. // on, regardless of the stream security layer (so XHTTP+REALITY+vlessenc
  715. // keeps its flow too).
  716. func vlessFlowAllowed(network, security string, settings map[string]any) bool {
  717. switch network {
  718. case "tcp":
  719. return security == "tls" || security == "reality"
  720. case "xhttp":
  721. return vlessEncryptionEnabled(settings)
  722. }
  723. return false
  724. }
  725. func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
  726. if inbound.Protocol != model.VLESS {
  727. return ""
  728. }
  729. address := s.resolveInboundAddress(inbound)
  730. stream := unmarshalStreamSettings(inbound.StreamSettings)
  731. client, ok := s.clientForLink(inbound, email)
  732. if !ok {
  733. return ""
  734. }
  735. uuid := client.ID
  736. port := inbound.Port
  737. streamNetwork, _ := stream["network"].(string)
  738. params := make(map[string]string)
  739. params["type"] = streamNetwork
  740. // Add encryption parameter for VLESS from inbound settings
  741. settings := s.linkSettings(inbound)
  742. if encryption, ok := settings["encryption"].(string); ok {
  743. params["encryption"] = encryption
  744. }
  745. applyShareNetworkParams(stream, streamNetwork, params)
  746. if finalmask, ok := stream["finalmask"].(map[string]any); ok {
  747. applyFinalMaskParams(finalmask, params)
  748. }
  749. security, _ := stream["security"].(string)
  750. switch security {
  751. case "tls":
  752. applyShareTLSParams(stream, params)
  753. case "reality":
  754. applyShareRealityParams(stream, params, subKey(client))
  755. default:
  756. params["security"] = "none"
  757. }
  758. if len(client.Flow) > 0 && vlessFlowAllowed(streamNetwork, security, settings) {
  759. params["flow"] = client.Flow
  760. }
  761. externalProxies, _ := stream["externalProxy"].([]any)
  762. if len(externalProxies) > 0 {
  763. return s.buildExternalProxyURLLinks(
  764. externalProxies,
  765. params,
  766. security,
  767. func(ep map[string]any, dest string, port int) string {
  768. return fmt.Sprintf("vless://%s@%s", applyVlessRoute(uuid, hostVlessRoute(ep)), joinHostPort(dest, port))
  769. },
  770. func(ep map[string]any) string {
  771. return s.endpointRemark(inbound, email, ep, streamNetwork)
  772. },
  773. )
  774. }
  775. link := fmt.Sprintf("vless://%s@%s", uuid, joinHostPort(address, port))
  776. return buildLinkWithParams(link, params, s.genRemark(inbound, email, "", streamNetwork))
  777. }
  778. func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string {
  779. if inbound.Protocol != model.Trojan {
  780. return ""
  781. }
  782. address := s.resolveInboundAddress(inbound)
  783. stream := unmarshalStreamSettings(inbound.StreamSettings)
  784. client, ok := s.clientForLink(inbound, email)
  785. if !ok {
  786. return ""
  787. }
  788. password := encodeUserinfo(client.Password)
  789. port := inbound.Port
  790. streamNetwork, _ := stream["network"].(string)
  791. params := make(map[string]string)
  792. params["type"] = streamNetwork
  793. applyShareNetworkParams(stream, streamNetwork, params)
  794. if finalmask, ok := stream["finalmask"].(map[string]any); ok {
  795. applyFinalMaskParams(finalmask, params)
  796. }
  797. security, _ := stream["security"].(string)
  798. switch security {
  799. case "tls":
  800. applyShareTLSParams(stream, params)
  801. case "reality":
  802. applyShareRealityParams(stream, params, subKey(client))
  803. if streamNetwork == "tcp" && len(client.Flow) > 0 {
  804. params["flow"] = client.Flow
  805. }
  806. default:
  807. params["security"] = "none"
  808. }
  809. externalProxies, _ := stream["externalProxy"].([]any)
  810. if len(externalProxies) > 0 {
  811. return s.buildExternalProxyURLLinks(
  812. externalProxies,
  813. params,
  814. security,
  815. func(_ map[string]any, dest string, port int) string {
  816. return fmt.Sprintf("trojan://%s@%s", password, joinHostPort(dest, port))
  817. },
  818. func(ep map[string]any) string {
  819. return s.endpointRemark(inbound, email, ep, streamNetwork)
  820. },
  821. )
  822. }
  823. link := fmt.Sprintf("trojan://%s@%s", password, joinHostPort(address, port))
  824. return buildLinkWithParams(link, params, s.genRemark(inbound, email, "", streamNetwork))
  825. }
  826. // encodeUserinfo percent-encodes a userinfo (password/auth) value so it
  827. // can be safely embedded in a `scheme://<value>@host:port` URL. RFC 3986
  828. // allows `=` in userinfo as a sub-delim, but several Trojan and Hysteria
  829. // clients reject share-links where the password contains literal `/`
  830. // or `=` (notably the common base64-with-padding shape produced by the
  831. // panel). Encode them too — this matches encodeURIComponent() on the
  832. // frontend and round-trips cleanly through net/url's parser.
  833. func encodeUserinfo(s string) string {
  834. return strings.ReplaceAll(url.QueryEscape(s), "+", "%20")
  835. }
  836. // joinHostPort wraps an IPv6 host in square brackets the way RFC 3986
  837. // requires for URI authorities, while leaving IPv4 addresses and hostnames
  838. // untouched. It also strips any brackets already present on the input so
  839. // callers don't have to normalize upstream.
  840. func joinHostPort(host string, port int) string {
  841. host = strings.Trim(host, "[]")
  842. return net.JoinHostPort(host, strconv.Itoa(port))
  843. }
  844. func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) string {
  845. if inbound.Protocol != model.Shadowsocks {
  846. return ""
  847. }
  848. address := s.resolveInboundAddress(inbound)
  849. stream := unmarshalStreamSettings(inbound.StreamSettings)
  850. client, ok := s.clientForLink(inbound, email)
  851. if !ok {
  852. return ""
  853. }
  854. settings := s.linkSettings(inbound)
  855. inboundPassword, _ := settings["password"].(string)
  856. method, _ := settings["method"].(string)
  857. streamNetwork, _ := stream["network"].(string)
  858. params := make(map[string]string)
  859. params["type"] = streamNetwork
  860. applyShareNetworkParams(stream, streamNetwork, params)
  861. if finalmask, ok := stream["finalmask"].(map[string]any); ok {
  862. applyFinalMaskParams(finalmask, params)
  863. }
  864. security, _ := stream["security"].(string)
  865. if security == "tls" {
  866. applyShareTLSParams(stream, params)
  867. }
  868. // SIP002 clients (v2rayN) ignore the xray-native type/headerType/host/path
  869. // params and only read `plugin`. Re-encode a TCP http header as obfs-local so
  870. // they build a matching tcp/http outbound (v2rayN forces request path "/").
  871. if streamNetwork == "tcp" && params["headerType"] == "http" {
  872. host := params["host"]
  873. delete(params, "type")
  874. delete(params, "headerType")
  875. delete(params, "host")
  876. delete(params, "path")
  877. params["plugin"] = "obfs-local;obfs=http;obfs-host=" + host
  878. }
  879. // SIP002 userinfo is base64(method:password). For SIP022 (2022-blake3-*) the
  880. // userinfo MUST NOT be base64-encoded; method and password are percent-encoded.
  881. var userInfo string
  882. if strings.HasPrefix(method, "2022") {
  883. userInfo = fmt.Sprintf("%s:%s:%s",
  884. url.QueryEscape(method),
  885. url.QueryEscape(inboundPassword),
  886. url.QueryEscape(client.Password))
  887. } else {
  888. userInfo = base64.RawURLEncoding.EncodeToString(fmt.Appendf(nil, "%s:%s", method, client.Password))
  889. }
  890. externalProxies, _ := stream["externalProxy"].([]any)
  891. if len(externalProxies) > 0 {
  892. proxyParams := cloneStringMap(params)
  893. proxyParams["security"] = security
  894. return s.buildExternalProxyURLLinks(
  895. externalProxies,
  896. proxyParams,
  897. security,
  898. func(_ map[string]any, dest string, port int) string {
  899. return fmt.Sprintf("ss://%s@%s", userInfo, joinHostPort(dest, port))
  900. },
  901. func(ep map[string]any) string {
  902. return s.endpointRemark(inbound, email, ep, streamNetwork)
  903. },
  904. )
  905. }
  906. link := fmt.Sprintf("ss://%s@%s", userInfo, joinHostPort(address, inbound.Port))
  907. return buildLinkWithParams(link, params, s.genRemark(inbound, email, "", streamNetwork))
  908. }
  909. func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) string {
  910. if inbound.Protocol != model.Hysteria {
  911. return ""
  912. }
  913. var stream map[string]any
  914. _ = json.Unmarshal([]byte(inbound.StreamSettings), &stream)
  915. client, ok := s.clientForLink(inbound, email)
  916. if !ok {
  917. return ""
  918. }
  919. auth := encodeUserinfo(client.Auth)
  920. params := make(map[string]string)
  921. params["security"] = "tls"
  922. tlsSetting, _ := stream["tlsSettings"].(map[string]any)
  923. alpns, _ := tlsSetting["alpn"].([]any)
  924. var alpn []string
  925. for _, a := range alpns {
  926. if s, ok := a.(string); ok {
  927. alpn = append(alpn, s)
  928. }
  929. }
  930. if len(alpn) > 0 {
  931. params["alpn"] = strings.Join(alpn, ",")
  932. }
  933. if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
  934. params["sni"], _ = sniValue.(string)
  935. }
  936. tlsSettings, _ := searchKey(tlsSetting, "settings")
  937. if tlsSetting != nil {
  938. if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
  939. params["fp"], _ = fpValue.(string)
  940. }
  941. if echValue, ok := searchKey(tlsSettings, "echConfigList"); ok {
  942. if ech, _ := echValue.(string); ech != "" {
  943. params["ech"] = ech
  944. }
  945. }
  946. if vcn, ok := verifyPeerCertByNameValue(tlsSettings); ok {
  947. params["vcn"] = vcn
  948. }
  949. if pins, ok := pinnedSha256List(tlsSettings); ok {
  950. for i, p := range pins {
  951. pins[i] = hysteriaPinHex(p)
  952. }
  953. params["pinSHA256"] = strings.Join(pins, ",")
  954. }
  955. }
  956. // salamander obfs (Hysteria2). Emit only the standard URI fields;
  957. // the non-standard fm=<json> finalmask dump breaks mihomo and other
  958. // Hysteria2 clients that reject unknown query params.
  959. if finalmask, ok := stream["finalmask"].(map[string]any); ok {
  960. if udpMasks, ok := finalmask["udp"].([]any); ok {
  961. for _, m := range udpMasks {
  962. mask, _ := m.(map[string]any)
  963. if mask == nil || mask["type"] != "salamander" {
  964. continue
  965. }
  966. settings, _ := mask["settings"].(map[string]any)
  967. if pw, ok := settings["password"].(string); ok && pw != "" {
  968. params["obfs"] = "salamander"
  969. params["obfs-password"] = pw
  970. break
  971. }
  972. }
  973. }
  974. }
  975. settings := s.linkSettings(inbound)
  976. version, _ := settings["version"].(float64)
  977. protocol := "hysteria2"
  978. if int(version) == 1 {
  979. protocol = "hysteria"
  980. }
  981. // Fan out one link per External Proxy entry if any. Previously this
  982. // generator ignored `externalProxy` entirely, so the link kept the
  983. // server's own IP/port even when the admin configured an alternate
  984. // endpoint (e.g. a CDN hostname + port that forwards to the node).
  985. // Matches the behaviour of genVlessLink / genTrojanLink / ….
  986. externalProxies, _ := stream["externalProxy"].([]any)
  987. if len(externalProxies) > 0 {
  988. links := make([]string, 0, len(externalProxies))
  989. for _, externalProxy := range externalProxies {
  990. ep, ok := externalProxy.(map[string]any)
  991. if !ok {
  992. continue
  993. }
  994. dest, _ := ep["dest"].(string)
  995. portF, okPort := ep["port"].(float64)
  996. if dest == "" || !okPort {
  997. continue
  998. }
  999. epParams := cloneStringMap(params)
  1000. applyExternalProxyHysteriaParams(ep, epParams)
  1001. link := fmt.Sprintf("%s://%s@%s", protocol, auth, joinHostPort(dest, int(portF)))
  1002. links = append(links, buildLinkWithParams(link, epParams, s.endpointRemark(inbound, email, ep, "quic")))
  1003. }
  1004. return strings.Join(links, "\n")
  1005. }
  1006. // No external proxy configured — use the inbound's resolved address so
  1007. // node-managed inbounds get the node's host instead of the central panel's.
  1008. if hopPorts := hysteriaHopPorts(stream); hopPorts != "" {
  1009. params["mport"] = hopPorts
  1010. }
  1011. link := fmt.Sprintf("%s://%s@%s", protocol, auth, joinHostPort(s.resolveInboundAddress(inbound), inbound.Port))
  1012. return buildLinkWithParams(link, params, s.genRemark(inbound, email, "", "quic"))
  1013. }
  1014. // hysteriaHopPorts returns the configured Hysteria2 UDP port-hopping range
  1015. // (finalmask.quicParams.udpHop.ports), or "" when port hopping is off. The
  1016. // range is emitted as the v2rayN-compatible `mport` query param; the URL port
  1017. // field stays numeric so .NET-Uri-based importers (v2rayN) can parse the link.
  1018. func hysteriaHopPorts(stream map[string]any) string {
  1019. finalmask, _ := stream["finalmask"].(map[string]any)
  1020. quicParams, _ := finalmask["quicParams"].(map[string]any)
  1021. udpHop, _ := quicParams["udpHop"].(map[string]any)
  1022. ports, _ := udpHop["ports"].(string)
  1023. return strings.TrimSpace(ports)
  1024. }
  1025. // loadNodes refreshes nodesByID from the DB. Called once per request so
  1026. // the per-inbound resolveInboundAddress lookups are pure map reads.
  1027. // We filter to address != ” so a half-configured node row doesn't
  1028. // accidentally produce a useless host like "https://:2053".
  1029. func (s *SubService) loadNodes() {
  1030. db := database.GetDB()
  1031. var nodes []*model.Node
  1032. if err := db.Model(&model.Node{}).Where("address != ''").Find(&nodes).Error; err != nil {
  1033. logger.Warning("subscription: load nodes failed:", err)
  1034. s.nodesByID = nil
  1035. return
  1036. }
  1037. m := make(map[int]*model.Node, len(nodes))
  1038. for _, n := range nodes {
  1039. m[n.Id] = n
  1040. }
  1041. s.nodesByID = m
  1042. }
  1043. // resolveInboundAddress picks the host an external client should connect to,
  1044. // honoring the inbound's share address strategy the same way the panel's
  1045. // share/QR link builder does (#5208):
  1046. // - "listen": an explicit, client-reachable bind Listen wins, backed by the
  1047. // node's address for node-managed inbounds;
  1048. // - "custom": the inbound's ShareAddr wins, then node, then listen;
  1049. // - "node" (default, and any unknown value): the node's address for
  1050. // node-managed inbounds, then a routable Listen — the pre-strategy order.
  1051. //
  1052. // Every chain ends at the admin's configured public host (Sub/Web domain) and
  1053. // then the subscriber's request host (s.address). Preferring the configured
  1054. // host over the request host for this last resort keeps a wildcard local inbound
  1055. // from advertising a bogus client IP that leaked into the request Host header
  1056. // behind NAT/proxy/CDN (#5425). A loopback/wildcard bind or a unix-domain-socket
  1057. // listen is a server-side detail and is never advertised; External Proxy still
  1058. // overrides everything upstream of this call.
  1059. func (s *SubService) resolveInboundAddress(inbound *model.Inbound) string {
  1060. var nodeAddr string
  1061. if inbound.NodeID != nil && s.nodesByID != nil {
  1062. if n, ok := s.nodesByID[*inbound.NodeID]; ok {
  1063. nodeAddr = n.Address
  1064. }
  1065. }
  1066. var listenAddr string
  1067. if listen := inbound.Listen; listen != "" && listen[0] != '@' && listen[0] != '/' && isRoutableHost(listen) {
  1068. listenAddr = listen
  1069. }
  1070. candidates := []string{nodeAddr, listenAddr}
  1071. switch inbound.ShareAddrStrategy {
  1072. case "listen":
  1073. candidates = []string{listenAddr, nodeAddr}
  1074. case "custom":
  1075. candidates = []string{strings.TrimSpace(inbound.ShareAddr), nodeAddr, listenAddr}
  1076. }
  1077. for _, c := range candidates {
  1078. if c != "" {
  1079. return c
  1080. }
  1081. }
  1082. if d := s.configuredPublicHost(); d != "" {
  1083. return d
  1084. }
  1085. return s.address
  1086. }
  1087. func findClientIndex(clients []model.Client, email string) int {
  1088. for i, client := range clients {
  1089. if client.Email == email {
  1090. return i
  1091. }
  1092. }
  1093. return -1
  1094. }
  1095. func unmarshalStreamSettings(streamSettings string) map[string]any {
  1096. var stream map[string]any
  1097. _ = json.Unmarshal([]byte(streamSettings), &stream)
  1098. return stream
  1099. }
  1100. func applyPathAndHostParams(settings map[string]any, params map[string]string) {
  1101. params["path"], _ = settings["path"].(string)
  1102. if host, ok := settings["host"].(string); ok && len(host) > 0 {
  1103. params["host"] = host
  1104. } else {
  1105. headers, _ := settings["headers"].(map[string]any)
  1106. params["host"] = searchHost(headers)
  1107. }
  1108. }
  1109. func applyPathAndHostObj(settings map[string]any, obj map[string]any) {
  1110. obj["path"], _ = settings["path"].(string)
  1111. if host, ok := settings["host"].(string); ok && len(host) > 0 {
  1112. obj["host"] = host
  1113. } else {
  1114. headers, _ := settings["headers"].(map[string]any)
  1115. obj["host"] = searchHost(headers)
  1116. }
  1117. }
  1118. func applyShareNetworkParams(stream map[string]any, streamNetwork string, params map[string]string) {
  1119. switch streamNetwork {
  1120. case "tcp":
  1121. tcp, _ := stream["tcpSettings"].(map[string]any)
  1122. header, _ := tcp["header"].(map[string]any)
  1123. typeStr, _ := header["type"].(string)
  1124. if typeStr == "http" {
  1125. request, _ := header["request"].(map[string]any)
  1126. requestPath, _ := request["path"].([]any)
  1127. if len(requestPath) > 0 {
  1128. params["path"], _ = requestPath[0].(string)
  1129. }
  1130. host := ""
  1131. if response, ok := header["response"].(map[string]any); ok {
  1132. if respHeaders, ok := response["headers"].(map[string]any); ok {
  1133. host = searchHost(respHeaders)
  1134. }
  1135. }
  1136. if host == "" {
  1137. headers, _ := request["headers"].(map[string]any)
  1138. host = searchHost(headers)
  1139. }
  1140. params["host"] = host
  1141. params["headerType"] = "http"
  1142. }
  1143. case "kcp":
  1144. applyKcpShareParams(stream, params)
  1145. case "ws":
  1146. ws, _ := stream["wsSettings"].(map[string]any)
  1147. applyPathAndHostParams(ws, params)
  1148. case "grpc":
  1149. grpc, _ := stream["grpcSettings"].(map[string]any)
  1150. params["serviceName"], _ = grpc["serviceName"].(string)
  1151. params["authority"], _ = grpc["authority"].(string)
  1152. if mm, _ := grpc["multiMode"].(bool); mm {
  1153. params["mode"] = "multi"
  1154. }
  1155. case "httpupgrade":
  1156. httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
  1157. applyPathAndHostParams(httpupgrade, params)
  1158. case "xhttp":
  1159. xhttp, _ := stream["xhttpSettings"].(map[string]any)
  1160. applyXhttpExtraParams(xhttp, params)
  1161. }
  1162. }
  1163. // applyXhttpExtraObj copies the bidirectional xhttp settings into the
  1164. // VMess base64 JSON link object. VMess supports arbitrary keys, so we
  1165. // flatten the SplitHTTPConfig "extra" fields directly onto obj.
  1166. func applyXhttpExtraObj(xhttp map[string]any, obj map[string]any) {
  1167. if xpb, ok := xhttp["xPaddingBytes"].(string); ok && len(xpb) > 0 {
  1168. obj["x_padding_bytes"] = xpb
  1169. }
  1170. maps.Copy(obj, buildXhttpExtra(xhttp))
  1171. }
  1172. func applyVmessNetworkParams(stream map[string]any, network string, obj map[string]any) {
  1173. obj["net"] = network
  1174. switch network {
  1175. case "tcp":
  1176. tcp, _ := stream["tcpSettings"].(map[string]any)
  1177. header, _ := tcp["header"].(map[string]any)
  1178. typeStr, _ := header["type"].(string)
  1179. obj["type"] = typeStr
  1180. if typeStr == "http" {
  1181. request, _ := header["request"].(map[string]any)
  1182. requestPath, _ := request["path"].([]any)
  1183. if len(requestPath) > 0 {
  1184. obj["path"], _ = requestPath[0].(string)
  1185. }
  1186. host := ""
  1187. if response, ok := header["response"].(map[string]any); ok {
  1188. if respHeaders, ok := response["headers"].(map[string]any); ok {
  1189. host = searchHost(respHeaders)
  1190. }
  1191. }
  1192. if host == "" {
  1193. headers, _ := request["headers"].(map[string]any)
  1194. host = searchHost(headers)
  1195. }
  1196. obj["host"] = host
  1197. }
  1198. case "kcp":
  1199. applyKcpShareObj(stream, obj)
  1200. case "ws":
  1201. ws, _ := stream["wsSettings"].(map[string]any)
  1202. applyPathAndHostObj(ws, obj)
  1203. case "grpc":
  1204. grpc, _ := stream["grpcSettings"].(map[string]any)
  1205. obj["path"], _ = grpc["serviceName"].(string)
  1206. obj["authority"], _ = grpc["authority"].(string)
  1207. if mm, _ := grpc["multiMode"].(bool); mm {
  1208. obj["type"] = "multi"
  1209. }
  1210. case "httpupgrade":
  1211. httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
  1212. applyPathAndHostObj(httpupgrade, obj)
  1213. case "xhttp":
  1214. xhttp, _ := stream["xhttpSettings"].(map[string]any)
  1215. applyPathAndHostObj(xhttp, obj)
  1216. if mode, ok := xhttp["mode"].(string); ok {
  1217. obj["mode"] = mode
  1218. }
  1219. applyXhttpExtraObj(xhttp, obj)
  1220. }
  1221. }
  1222. func applyShareTLSParams(stream map[string]any, params map[string]string) {
  1223. params["security"] = "tls"
  1224. tlsSetting, _ := stream["tlsSettings"].(map[string]any)
  1225. alpns, _ := tlsSetting["alpn"].([]any)
  1226. var alpn []string
  1227. for _, a := range alpns {
  1228. if s, ok := a.(string); ok {
  1229. alpn = append(alpn, s)
  1230. }
  1231. }
  1232. if len(alpn) > 0 {
  1233. params["alpn"] = strings.Join(alpn, ",")
  1234. }
  1235. if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
  1236. params["sni"], _ = sniValue.(string)
  1237. }
  1238. tlsSettings, _ := searchKey(tlsSetting, "settings")
  1239. if tlsSetting != nil {
  1240. if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
  1241. params["fp"], _ = fpValue.(string)
  1242. }
  1243. if echValue, ok := searchKey(tlsSettings, "echConfigList"); ok {
  1244. if ech, _ := echValue.(string); ech != "" {
  1245. params["ech"] = ech
  1246. }
  1247. }
  1248. if vcn, ok := verifyPeerCertByNameValue(tlsSettings); ok {
  1249. params["vcn"] = vcn
  1250. }
  1251. if pins, ok := pinnedSha256List(tlsSettings); ok {
  1252. params["pcs"] = strings.Join(pins, ",")
  1253. }
  1254. }
  1255. }
  1256. func applyVmessTLSParams(stream map[string]any, obj map[string]any) {
  1257. tlsSetting, _ := stream["tlsSettings"].(map[string]any)
  1258. alpns, _ := tlsSetting["alpn"].([]any)
  1259. if len(alpns) > 0 {
  1260. var alpn []string
  1261. for _, a := range alpns {
  1262. if s, ok := a.(string); ok {
  1263. alpn = append(alpn, s)
  1264. }
  1265. }
  1266. obj["alpn"] = strings.Join(alpn, ",")
  1267. }
  1268. if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
  1269. obj["sni"], _ = sniValue.(string)
  1270. }
  1271. tlsSettings, _ := searchKey(tlsSetting, "settings")
  1272. if tlsSetting != nil {
  1273. if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
  1274. obj["fp"], _ = fpValue.(string)
  1275. }
  1276. if echValue, ok := searchKey(tlsSettings, "echConfigList"); ok {
  1277. if ech, _ := echValue.(string); ech != "" {
  1278. obj["ech"] = ech
  1279. }
  1280. }
  1281. if vcn, ok := verifyPeerCertByNameValue(tlsSettings); ok {
  1282. obj["vcn"] = vcn
  1283. }
  1284. if pins, ok := pinnedSha256List(tlsSettings); ok {
  1285. obj["pcs"] = strings.Join(pins, ",")
  1286. }
  1287. }
  1288. }
  1289. // verifyPeerCertByNameValue extracts tlsSettings.settings.verifyPeerCertByName
  1290. // (the v2rayN `vcn` param) as a trimmed string. Like pinnedPeerCertSha256 it is
  1291. // panel-only and flows into share links so clients verify the server
  1292. // certificate by this name — the replacement for the removed allowInsecure.
  1293. func verifyPeerCertByNameValue(tlsClientSettings any) (string, bool) {
  1294. raw, ok := searchKey(tlsClientSettings, "verifyPeerCertByName")
  1295. if !ok {
  1296. return "", false
  1297. }
  1298. s, ok := raw.(string)
  1299. if !ok {
  1300. return "", false
  1301. }
  1302. if s = strings.TrimSpace(s); s == "" {
  1303. return "", false
  1304. }
  1305. return s, true
  1306. }
  1307. // pinnedSha256List extracts tlsSettings.settings.pinnedPeerCertSha256 as a
  1308. // []string. The field is panel-only (stripped before the run-config reaches
  1309. // xray-core via internal/web/service/xray.go) but flows into share links so clients
  1310. // can pin the server's certificate hash.
  1311. func pinnedSha256List(tlsClientSettings any) ([]string, bool) {
  1312. raw, ok := searchKey(tlsClientSettings, "pinnedPeerCertSha256")
  1313. if !ok {
  1314. return nil, false
  1315. }
  1316. arr, ok := raw.([]any)
  1317. if !ok || len(arr) == 0 {
  1318. return nil, false
  1319. }
  1320. out := make([]string, 0, len(arr))
  1321. for _, v := range arr {
  1322. s, ok := v.(string)
  1323. if !ok || s == "" {
  1324. continue
  1325. }
  1326. out = append(out, s)
  1327. }
  1328. if len(out) == 0 {
  1329. return nil, false
  1330. }
  1331. return out, true
  1332. }
  1333. // hysteriaPinHex normalises a pinnedPeerCertSha256 entry into the 64-character
  1334. // lowercase hex form that Xray-core's Hysteria2 pinSHA256 parser requires.
  1335. //
  1336. // The panel stores pins in several shapes: base64 (xray-core's native TLS
  1337. // format, used by the generate button and the JSON subscription) and hex —
  1338. // either bare or colon-separated as `openssl x509 -fingerprint -sha256` emits
  1339. // it. Hysteria2 clients hex-decode pinSHA256 and crash on a base64 value, so
  1340. // each entry is coerced to bare hex here. Anything that is neither a 32-byte
  1341. // hex nor a 32-byte base64 SHA-256 is returned unchanged so unexpected data is
  1342. // not silently dropped. Mirrors decodeCertPin in internal/web/service/node.go.
  1343. func hysteriaPinHex(pin string) string {
  1344. pin = strings.TrimSpace(pin)
  1345. if h := strings.ReplaceAll(pin, ":", ""); len(h) == hex.EncodedLen(sha256.Size) {
  1346. if _, err := hex.DecodeString(h); err == nil {
  1347. return strings.ToLower(h)
  1348. }
  1349. }
  1350. for _, enc := range []*base64.Encoding{
  1351. base64.StdEncoding,
  1352. base64.RawStdEncoding,
  1353. base64.URLEncoding,
  1354. base64.RawURLEncoding,
  1355. } {
  1356. if b, err := enc.DecodeString(pin); err == nil && len(b) == sha256.Size {
  1357. return hex.EncodeToString(b)
  1358. }
  1359. }
  1360. return pin
  1361. }
  1362. func applyShareRealityParams(stream map[string]any, params map[string]string, clientKey string) {
  1363. params["security"] = "reality"
  1364. realitySetting, _ := stream["realitySettings"].(map[string]any)
  1365. realitySettings, _ := searchKey(realitySetting, "settings")
  1366. if realitySetting != nil {
  1367. if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
  1368. if sNames, _ := sniValue.([]any); len(sNames) > 0 {
  1369. params["sni"], _ = sNames[random.Num(len(sNames))].(string)
  1370. }
  1371. }
  1372. if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
  1373. params["pbk"], _ = pbkValue.(string)
  1374. }
  1375. if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
  1376. if shortIds, _ := sidValue.([]any); len(shortIds) > 0 {
  1377. params["sid"], _ = shortIds[random.Num(len(shortIds))].(string)
  1378. }
  1379. }
  1380. if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
  1381. if fp, ok := fpValue.(string); ok && len(fp) > 0 {
  1382. params["fp"] = fp
  1383. }
  1384. }
  1385. if pqvValue, ok := searchKey(realitySettings, "mldsa65Verify"); ok {
  1386. if pqv, ok := pqvValue.(string); ok && len(pqv) > 0 {
  1387. params["pqv"] = pqv
  1388. }
  1389. }
  1390. seed := ""
  1391. if spxValue, ok := searchKey(realitySettings, "spiderX"); ok {
  1392. seed, _ = spxValue.(string)
  1393. }
  1394. params["spx"] = deriveSpiderX(seed, clientKey)
  1395. }
  1396. }
  1397. // subKey returns a stable per-client identity for deterministic derivations,
  1398. // preferring the subscription id and falling back to the (unique) email.
  1399. func subKey(c model.Client) string {
  1400. if c.SubID != "" {
  1401. return c.SubID
  1402. }
  1403. return c.Email
  1404. }
  1405. // deriveSpiderX maps the inbound's spiderX seed plus a stable client key to a
  1406. // deterministic per-client "/path"; frontend/src/lib/xray/spider-x.ts mirrors it.
  1407. func deriveSpiderX(seed, clientKey string) string {
  1408. if seed == "" && clientKey == "" {
  1409. return "/" + random.Seq(15)
  1410. }
  1411. sum := sha256.Sum256([]byte(seed + "|" + clientKey))
  1412. return "/" + hex.EncodeToString(sum[:])[:15]
  1413. }
  1414. func buildVmessLink(obj map[string]any) string {
  1415. jsonStr, _ := json.MarshalIndent(obj, "", " ")
  1416. return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
  1417. }
  1418. func cloneVmessShareObj(baseObj map[string]any, newSecurity string) map[string]any {
  1419. newObj := map[string]any{}
  1420. for key, value := range baseObj {
  1421. if newSecurity != "none" || (key != "alpn" && key != "sni" && key != "fp" && key != "pcs") {
  1422. newObj[key] = value
  1423. }
  1424. }
  1425. return newObj
  1426. }
  1427. func applyExternalProxyTLSObj(ep map[string]any, obj map[string]any, security string) {
  1428. if security != "tls" {
  1429. return
  1430. }
  1431. if sni, ok := externalProxySNI(ep); ok {
  1432. obj["sni"] = sni
  1433. }
  1434. if fp, ok := ep["fingerprint"].(string); ok && fp != "" {
  1435. obj["fp"] = fp
  1436. }
  1437. if alpn, ok := externalProxyALPN(ep["alpn"]); ok {
  1438. obj["alpn"] = alpn
  1439. }
  1440. if pins, ok := externalProxyPins(ep["pinnedPeerCertSha256"]); ok {
  1441. obj["pcs"] = joinAnyStrings(pins)
  1442. }
  1443. if vcn, ok := ep["verifyPeerCertByName"].(string); ok && vcn != "" {
  1444. obj["vcn"] = vcn
  1445. }
  1446. if ech, ok := ep["echConfigList"].(string); ok && ech != "" {
  1447. obj["ech"] = ech
  1448. }
  1449. }
  1450. func applyExternalProxyTLSParams(ep map[string]any, params map[string]string, security string) {
  1451. if security != "tls" {
  1452. return
  1453. }
  1454. if sni, ok := externalProxySNI(ep); ok {
  1455. params["sni"] = sni
  1456. }
  1457. if fp, ok := ep["fingerprint"].(string); ok && fp != "" {
  1458. params["fp"] = fp
  1459. }
  1460. if alpn, ok := externalProxyALPN(ep["alpn"]); ok {
  1461. params["alpn"] = alpn
  1462. }
  1463. if pins, ok := externalProxyPins(ep["pinnedPeerCertSha256"]); ok {
  1464. params["pcs"] = joinAnyStrings(pins)
  1465. }
  1466. if vcn, ok := ep["verifyPeerCertByName"].(string); ok && vcn != "" {
  1467. params["vcn"] = vcn
  1468. }
  1469. if ech, ok := ep["echConfigList"].(string); ok && ech != "" {
  1470. params["ech"] = ech
  1471. }
  1472. }
  1473. // applyExternalProxyHysteriaParams overrides the cert pin for a single
  1474. // external-proxy entry on a Hysteria link. Hysteria carries the pin as a hex
  1475. // `pinSHA256` (not the `pcs` the URL-param protocols use), so each entry is
  1476. // coerced through hysteriaPinHex like the main pin. sni/fp/alpn are left as
  1477. // the inbound's own — Hysteria external proxies are typically alternate
  1478. // endpoints (port-hop / CDN) fronting the same certificate.
  1479. func applyExternalProxyHysteriaParams(ep map[string]any, params map[string]string) {
  1480. if pins, ok := externalProxyPins(ep["pinnedPeerCertSha256"]); ok {
  1481. hexPins := make([]string, 0, len(pins))
  1482. for _, p := range pins {
  1483. if s, ok := p.(string); ok {
  1484. hexPins = append(hexPins, hysteriaPinHex(s))
  1485. }
  1486. }
  1487. params["pinSHA256"] = strings.Join(hexPins, ",")
  1488. }
  1489. if ai, ok := ep["allowInsecure"].(bool); ok && ai {
  1490. params["insecure"] = "1"
  1491. }
  1492. }
  1493. // cloneStreamForExternalProxy returns a shallow clone of stream with
  1494. // tlsSettings (and its nested settings map) deep-copied. The external
  1495. // proxy loop mutates tlsSettings per iteration, so without isolating
  1496. // those maps each proxy's SNI/fingerprint/ALPN would leak into the next.
  1497. func cloneStreamForExternalProxy(stream map[string]any) map[string]any {
  1498. out := cloneMap(stream)
  1499. ts, ok := out["tlsSettings"].(map[string]any)
  1500. if !ok || ts == nil {
  1501. return out
  1502. }
  1503. clonedTs := cloneMap(ts)
  1504. if inner, ok := clonedTs["settings"].(map[string]any); ok && inner != nil {
  1505. clonedTs["settings"] = cloneMap(inner)
  1506. }
  1507. out["tlsSettings"] = clonedTs
  1508. return out
  1509. }
  1510. func applyExternalProxyTLSToStream(ep map[string]any, stream map[string]any, security string) {
  1511. if security != "tls" {
  1512. return
  1513. }
  1514. tlsSettings, _ := stream["tlsSettings"].(map[string]any)
  1515. if tlsSettings == nil {
  1516. tlsSettings = map[string]any{}
  1517. stream["tlsSettings"] = tlsSettings
  1518. }
  1519. if sni, ok := externalProxySNI(ep); ok {
  1520. tlsSettings["serverName"] = sni
  1521. }
  1522. if fp, ok := ep["fingerprint"].(string); ok && fp != "" {
  1523. tlsSettings["fingerprint"] = fp
  1524. settings, _ := tlsSettings["settings"].(map[string]any)
  1525. if settings == nil {
  1526. settings = map[string]any{}
  1527. tlsSettings["settings"] = settings
  1528. }
  1529. settings["fingerprint"] = fp
  1530. }
  1531. if alpn, ok := externalProxyALPNList(ep["alpn"]); ok {
  1532. tlsSettings["alpn"] = alpn
  1533. }
  1534. if pins, ok := externalProxyPins(ep["pinnedPeerCertSha256"]); ok {
  1535. settings, _ := tlsSettings["settings"].(map[string]any)
  1536. if settings == nil {
  1537. settings = map[string]any{}
  1538. tlsSettings["settings"] = settings
  1539. }
  1540. settings["pinnedPeerCertSha256"] = pins
  1541. }
  1542. if ech, ok := ep["echConfigList"].(string); ok && ech != "" {
  1543. settings, _ := tlsSettings["settings"].(map[string]any)
  1544. if settings == nil {
  1545. settings = map[string]any{}
  1546. tlsSettings["settings"] = settings
  1547. }
  1548. settings["echConfigList"] = ech
  1549. }
  1550. if vcn, ok := ep["verifyPeerCertByName"].(string); ok && vcn != "" {
  1551. settings, _ := tlsSettings["settings"].(map[string]any)
  1552. if settings == nil {
  1553. settings = map[string]any{}
  1554. tlsSettings["settings"] = settings
  1555. }
  1556. settings["verifyPeerCertByName"] = vcn
  1557. }
  1558. if ai, ok := ep["allowInsecure"].(bool); ok && ai {
  1559. settings, _ := tlsSettings["settings"].(map[string]any)
  1560. if settings == nil {
  1561. settings = map[string]any{}
  1562. tlsSettings["settings"] = settings
  1563. }
  1564. settings["allowInsecure"] = true
  1565. }
  1566. }
  1567. func externalProxySNI(ep map[string]any) (string, bool) {
  1568. if sni, ok := ep["sni"].(string); ok && sni != "" {
  1569. return sni, true
  1570. }
  1571. return "", false
  1572. }
  1573. func externalProxyALPN(value any) (string, bool) {
  1574. switch v := value.(type) {
  1575. case string:
  1576. return v, v != ""
  1577. case []string:
  1578. if len(v) == 0 {
  1579. return "", false
  1580. }
  1581. return strings.Join(v, ","), true
  1582. case []any:
  1583. alpn := make([]string, 0, len(v))
  1584. for _, item := range v {
  1585. if s, ok := item.(string); ok && s != "" {
  1586. alpn = append(alpn, s)
  1587. }
  1588. }
  1589. if len(alpn) == 0 {
  1590. return "", false
  1591. }
  1592. return strings.Join(alpn, ","), true
  1593. default:
  1594. return "", false
  1595. }
  1596. }
  1597. func externalProxyALPNList(value any) ([]any, bool) {
  1598. switch v := value.(type) {
  1599. case string:
  1600. if v == "" {
  1601. return nil, false
  1602. }
  1603. parts := strings.Split(v, ",")
  1604. out := make([]any, 0, len(parts))
  1605. for _, part := range parts {
  1606. if part = strings.TrimSpace(part); part != "" {
  1607. out = append(out, part)
  1608. }
  1609. }
  1610. return out, len(out) > 0
  1611. case []string:
  1612. out := make([]any, 0, len(v))
  1613. for _, item := range v {
  1614. if item != "" {
  1615. out = append(out, item)
  1616. }
  1617. }
  1618. return out, len(out) > 0
  1619. case []any:
  1620. out := make([]any, 0, len(v))
  1621. for _, item := range v {
  1622. if s, ok := item.(string); ok && s != "" {
  1623. out = append(out, s)
  1624. }
  1625. }
  1626. return out, len(out) > 0
  1627. default:
  1628. return nil, false
  1629. }
  1630. }
  1631. // externalProxyPins extracts an external-proxy entry's pinnedPeerCertSha256
  1632. // as a []any of non-empty strings. The []any element type matches what the
  1633. // JSON/Clash sub builders expect when reading the value back off the cloned
  1634. // stream's tlsSettings.settings.
  1635. func externalProxyPins(value any) ([]any, bool) {
  1636. switch v := value.(type) {
  1637. case []string:
  1638. out := make([]any, 0, len(v))
  1639. for _, item := range v {
  1640. if item != "" {
  1641. out = append(out, item)
  1642. }
  1643. }
  1644. return out, len(out) > 0
  1645. case []any:
  1646. out := make([]any, 0, len(v))
  1647. for _, item := range v {
  1648. if s, ok := item.(string); ok && s != "" {
  1649. out = append(out, s)
  1650. }
  1651. }
  1652. return out, len(out) > 0
  1653. default:
  1654. return nil, false
  1655. }
  1656. }
  1657. func joinAnyStrings(items []any) string {
  1658. parts := make([]string, 0, len(items))
  1659. for _, item := range items {
  1660. if s, ok := item.(string); ok {
  1661. parts = append(parts, s)
  1662. }
  1663. }
  1664. return strings.Join(parts, ",")
  1665. }
  1666. // buildVmessExternalProxyLinks is a thin adapter: it maps the legacy
  1667. // externalProxy entries to []ShareEndpoint and renders them through the unified
  1668. // endpoint path. Kept as a thin shim over the unified endpoint builder so
  1669. // genVmessLink keeps calling one helper (now threading transport through).
  1670. func (s *SubService) buildVmessExternalProxyLinks(externalProxies []any, baseObj map[string]any, inbound *model.Inbound, email string, transport string) string {
  1671. eps := make([]ShareEndpoint, 0, len(externalProxies))
  1672. for _, externalProxy := range externalProxies {
  1673. ep, _ := externalProxy.(map[string]any)
  1674. eps = append(eps, externalProxyToEndpoint(ep))
  1675. }
  1676. return s.buildEndpointVmessLinks(eps, baseObj, inbound, email, transport)
  1677. }
  1678. // buildLinkWithParams appends ?query and #fragment to a pre-built
  1679. // scheme://userinfo@host:port string without re-parsing it. The caller
  1680. // has already escaped userinfo via encodeUserinfo (or chosen a base64
  1681. // alphabet with no reserved chars); a url.Parse + .String() round-trip
  1682. // would silently decode that escaping because Go's userinfo emitter
  1683. // leaves sub-delims (=, +, ;) literal, which breaks Trojan/Hysteria/SS
  1684. // clients that reject those chars in the password.
  1685. func buildLinkWithParams(link string, params map[string]string, fragment string) string {
  1686. return appendQueryAndFragment(link, params, fragment, "", false)
  1687. }
  1688. // buildLinkWithParamsAndSecurity is buildLinkWithParams plus an
  1689. // external-proxy override: the `security` key in params is replaced with
  1690. // the supplied value, and TLS hint fields (alpn/sni/fp/pcs) are stripped
  1691. // when the override is `none`.
  1692. func buildLinkWithParamsAndSecurity(link string, params map[string]string, fragment, security string, omitTLSFields bool) string {
  1693. return appendQueryAndFragment(link, params, fragment, security, omitTLSFields)
  1694. }
  1695. func appendQueryAndFragment(link string, params map[string]string, fragment, securityOverride string, omitTLSFields bool) string {
  1696. var sb strings.Builder
  1697. sb.WriteString(link)
  1698. if len(params) > 0 {
  1699. q := url.Values{}
  1700. for k, v := range params {
  1701. if securityOverride != "" && k == "security" {
  1702. v = securityOverride
  1703. }
  1704. if omitTLSFields && (k == "alpn" || k == "sni" || k == "fp" || k == "pcs") {
  1705. continue
  1706. }
  1707. q.Set(k, v)
  1708. }
  1709. encoded := q.Encode()
  1710. if encoded != "" {
  1711. if strings.Contains(link, "?") {
  1712. sb.WriteByte('&')
  1713. } else {
  1714. sb.WriteByte('?')
  1715. }
  1716. sb.WriteString(encoded)
  1717. }
  1718. }
  1719. if fragment != "" {
  1720. sb.WriteByte('#')
  1721. // Match the frontend's encodeURIComponent(remark): spaces become
  1722. // %20 (not + as in query strings).
  1723. sb.WriteString(strings.ReplaceAll(url.QueryEscape(fragment), "+", "%20"))
  1724. }
  1725. return sb.String()
  1726. }
  1727. // buildExternalProxyURLLinks is a thin adapter: it maps the legacy externalProxy
  1728. // entries to []ShareEndpoint and renders them through the unified endpoint path.
  1729. // Kept so the genVless/genTrojan/genShadowsocks call sites are unchanged.
  1730. func (s *SubService) buildExternalProxyURLLinks(
  1731. externalProxies []any,
  1732. params map[string]string,
  1733. baseSecurity string,
  1734. makeLink func(ep map[string]any, dest string, port int) string,
  1735. makeRemark func(ep map[string]any) string,
  1736. ) string {
  1737. eps := make([]ShareEndpoint, 0, len(externalProxies))
  1738. for _, externalProxy := range externalProxies {
  1739. ep, _ := externalProxy.(map[string]any)
  1740. eps = append(eps, externalProxyToEndpoint(ep))
  1741. }
  1742. return s.buildEndpointLinks(eps, params, baseSecurity, func(e ShareEndpoint) string {
  1743. return makeLink(e.ep, e.Address, e.Port)
  1744. }, func(e ShareEndpoint) string {
  1745. return makeRemark(e.ep)
  1746. })
  1747. }
  1748. func cloneStringMap(source map[string]string) map[string]string {
  1749. cloned := make(map[string]string, len(source))
  1750. maps.Copy(cloned, source)
  1751. return cloned
  1752. }
  1753. // genRemark builds the remark for a non-host link (raw default / legacy
  1754. // externalProxy / synthetic JSON-Clash entry). A set remark template drives it
  1755. // in both the body and display contexts (genTemplatedRemark renders the
  1756. // name-only part on displays); with no template it falls back to the inbound
  1757. // remark, extra and email joined by "-".
  1758. func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string, transport string) string {
  1759. if s.remarkTemplate != "" {
  1760. return s.genTemplatedRemark(inbound, s.lookupClient(inbound, email), extra, transport)
  1761. }
  1762. return fallbackRemark(inbound.Remark, extra, email)
  1763. }
  1764. func fallbackRemark(parts ...string) string {
  1765. out := make([]string, 0, len(parts))
  1766. for _, p := range parts {
  1767. if p != "" {
  1768. out = append(out, p)
  1769. }
  1770. }
  1771. return strings.Join(out, "-")
  1772. }
  1773. // findClientStats returns the inbound's traffic record for email, if present.
  1774. func (s *SubService) findClientStats(inbound *model.Inbound, email string) (xray.ClientTraffic, bool) {
  1775. for _, clientStat := range inbound.ClientStats {
  1776. if clientStat.Email == email {
  1777. return clientStat, true
  1778. }
  1779. }
  1780. return xray.ClientTraffic{}, false
  1781. }
  1782. // statsByEmailFromDB resolves a client's traffic row straight from the DB by its
  1783. // globally-unique email, caching the hit into statsByEmail for the rest of the
  1784. // request. It's the last-resort lookup behind statsForClient: the preloaded
  1785. // ClientStats and the statsByEmail index are both keyed by
  1786. // client_traffics.inbound_id, which is written once by AddClientStat and never
  1787. // updated. When an inbound is deleted and recreated it gets a new id, so the old
  1788. // row is orphaned from every loaded inbound and both in-memory paths miss —
  1789. // leaving {{TRAFFIC_USED}} stuck at 0 for pre-existing clients even though their
  1790. // usage is intact (#5567). Matching by email recovers it, the same way the
  1791. // sub-info header's AggregateTrafficByEmails already does.
  1792. func (s *SubService) statsByEmailFromDB(email string) (xray.ClientTraffic, bool) {
  1793. db := database.GetDB()
  1794. if db == nil {
  1795. return xray.ClientTraffic{}, false
  1796. }
  1797. var row xray.ClientTraffic
  1798. if err := db.Model(&xray.ClientTraffic{}).Where("email = ?", email).First(&row).Error; err != nil {
  1799. return xray.ClientTraffic{}, false
  1800. }
  1801. if s.statsByEmail == nil {
  1802. s.statsByEmail = map[string]xray.ClientTraffic{}
  1803. }
  1804. s.statsByEmail[email] = row
  1805. return row, true
  1806. }
  1807. func searchKey(data any, key string) (any, bool) {
  1808. switch val := data.(type) {
  1809. case map[string]any:
  1810. for k, v := range val {
  1811. if k == key {
  1812. return v, true
  1813. }
  1814. if result, ok := searchKey(v, key); ok {
  1815. return result, true
  1816. }
  1817. }
  1818. case []any:
  1819. for _, v := range val {
  1820. if result, ok := searchKey(v, key); ok {
  1821. return result, true
  1822. }
  1823. }
  1824. }
  1825. return nil, false
  1826. }
  1827. // buildXhttpExtra walks an xhttpSettings map and returns the JSON blob
  1828. // that goes into the URL's `extra` param (or, for VMess, the link
  1829. // object). Carries ONLY the bidirectional fields from xray-core's
  1830. // SplitHTTPConfig — i.e. the ones the server enforces and the client
  1831. // must match. Strictly one-sided fields are excluded:
  1832. //
  1833. // - server-only (noSSEHeader, scMaxBufferedPosts, scStreamUpServerSecs,
  1834. // serverMaxHeaderBytes) — client wouldn't read them, so emitting
  1835. // them just bloats the URL.
  1836. // - client-only values are included only when present in the inbound
  1837. // JSON. Some deployments/imported configs carry them there, and the
  1838. // subscription link is the only place clients can receive them.
  1839. //
  1840. // Truthy-only guards keep default inbounds emitting the same compact URL
  1841. // they did before this helper grew.
  1842. func buildXhttpExtra(xhttp map[string]any) map[string]any {
  1843. if xhttp == nil {
  1844. return nil
  1845. }
  1846. extra := map[string]any{}
  1847. if mode, ok := xhttp["mode"].(string); ok && len(mode) > 0 {
  1848. extra["mode"] = mode
  1849. }
  1850. if xpb, ok := xhttp["xPaddingBytes"].(string); ok && len(xpb) > 0 {
  1851. extra["xPaddingBytes"] = xpb
  1852. }
  1853. if obfs, ok := xhttp["xPaddingObfsMode"].(bool); ok && obfs {
  1854. extra["xPaddingObfsMode"] = true
  1855. for _, field := range []string{"xPaddingKey", "xPaddingHeader", "xPaddingPlacement", "xPaddingMethod"} {
  1856. if v, ok := xhttp[field].(string); ok && len(v) > 0 {
  1857. extra[field] = v
  1858. }
  1859. }
  1860. }
  1861. stringFields := []string{
  1862. "uplinkHTTPMethod",
  1863. "sessionIDPlacement", "sessionIDKey", "sessionIDTable", "sessionIDLength",
  1864. "seqPlacement", "seqKey",
  1865. "uplinkDataPlacement", "uplinkDataKey",
  1866. "scMaxEachPostBytes", "scMinPostsIntervalMs",
  1867. }
  1868. // Values matching xray-core's own defaults are redundant on the wire and
  1869. // the literal scMinPostsIntervalMs=30 is a known DPI fingerprint (#5141).
  1870. // Old panels seeded these defaults into every xhttp inbound, so filter
  1871. // them here instead of requiring every stored config to be re-saved.
  1872. coreDefaults := map[string]string{
  1873. "scMaxEachPostBytes": "1000000",
  1874. "scMinPostsIntervalMs": "30",
  1875. }
  1876. for _, field := range stringFields {
  1877. if v, ok := xhttp[field].(string); ok && len(v) > 0 && v != coreDefaults[field] {
  1878. extra[field] = v
  1879. }
  1880. }
  1881. // Legacy inbounds (pre xray-core #6258) stored sessionPlacement/sessionKey.
  1882. // Lift them onto the renamed keys so links from not-yet-resaved configs
  1883. // still carry the session settings. Mirrors the frontend migration.
  1884. for legacy, renamed := range map[string]string{
  1885. "sessionPlacement": "sessionIDPlacement",
  1886. "sessionKey": "sessionIDKey",
  1887. } {
  1888. if _, exists := extra[renamed]; !exists {
  1889. if v, ok := xhttp[legacy].(string); ok && len(v) > 0 {
  1890. extra[renamed] = v
  1891. }
  1892. }
  1893. }
  1894. for _, field := range []string{"uplinkChunkSize"} {
  1895. if v, ok := nonZeroShareValue(xhttp[field]); ok {
  1896. extra[field] = v
  1897. }
  1898. }
  1899. for _, field := range []string{"noGRPCHeader"} {
  1900. if v, ok := xhttp[field].(bool); ok && v {
  1901. extra[field] = v
  1902. }
  1903. }
  1904. for _, field := range []string{"xmux", "downloadSettings"} {
  1905. if v, ok := nonEmptyShareObject(xhttp[field]); ok {
  1906. extra[field] = v
  1907. }
  1908. }
  1909. // Headers — emitted as the {name: value} map upstream's struct
  1910. // expects. The server runtime ignores this field, but the client
  1911. // (consuming the share link) honors it. Drop any "host" entry —
  1912. // host already wins as a top-level URL param.
  1913. if rawHeaders, ok := xhttp["headers"].(map[string]any); ok && len(rawHeaders) > 0 {
  1914. out := map[string]any{}
  1915. for k, v := range rawHeaders {
  1916. if strings.EqualFold(k, "host") {
  1917. continue
  1918. }
  1919. out[k] = v
  1920. }
  1921. if len(out) > 0 {
  1922. extra["headers"] = out
  1923. }
  1924. }
  1925. if len(extra) == 0 {
  1926. return nil
  1927. }
  1928. return extra
  1929. }
  1930. func nonZeroShareValue(v any) (any, bool) {
  1931. switch value := v.(type) {
  1932. case string:
  1933. return value, value != ""
  1934. case int:
  1935. return value, value != 0
  1936. case int32:
  1937. return value, value != 0
  1938. case int64:
  1939. return value, value != 0
  1940. case float32:
  1941. return value, value != 0
  1942. case float64:
  1943. return value, value != 0
  1944. default:
  1945. return nil, false
  1946. }
  1947. }
  1948. func nonEmptyShareObject(v any) (any, bool) {
  1949. switch value := v.(type) {
  1950. case map[string]any:
  1951. return value, len(value) > 0
  1952. case map[string]string:
  1953. return value, len(value) > 0
  1954. case []any:
  1955. return value, len(value) > 0
  1956. default:
  1957. return nil, false
  1958. }
  1959. }
  1960. // applyXhttpExtraParams emits the full xhttp config into the URL query
  1961. // params of a vless:// / trojan:// / ss:// link. Sets path/host/mode at
  1962. // top level (xray's Build() always lets these win over `extra`) and packs
  1963. // everything else into a JSON `extra` param. Also writes the flat
  1964. // `x_padding_bytes` param sing-box-family clients understand.
  1965. //
  1966. // Without this, the admin's custom xPaddingBytes / sessionKey / etc. never
  1967. // reach the client and handshakes are silently rejected with
  1968. // `invalid padding (...) length: 0` — the client-visible symptom is
  1969. // "xhttp doesn't connect" on OpenWRT / sing-box.
  1970. //
  1971. // Two encodings are written so every popular client can read at least one:
  1972. //
  1973. // - x_padding_bytes=<range> — flat param, understood by sing-box and its
  1974. // derivatives (Podkop, OpenWRT sing-box, Karing, NekoBox, …).
  1975. // - extra=<url-encoded-json> — full xhttp settings blob, which is how
  1976. // xray-core clients (v2rayNG, Happ, Furious, Exclave, …) pick up the
  1977. // bidirectional fields beyond path/host/mode.
  1978. func applyXhttpExtraParams(xhttp map[string]any, params map[string]string) {
  1979. if xhttp == nil {
  1980. return
  1981. }
  1982. applyPathAndHostParams(xhttp, params)
  1983. if mode, ok := xhttp["mode"].(string); ok {
  1984. params["mode"] = mode
  1985. }
  1986. if xpb, ok := xhttp["xPaddingBytes"].(string); ok && len(xpb) > 0 {
  1987. params["x_padding_bytes"] = xpb
  1988. }
  1989. extra := buildXhttpExtra(xhttp)
  1990. if extra != nil {
  1991. if b, err := json.Marshal(extra); err == nil {
  1992. params["extra"] = string(b)
  1993. }
  1994. }
  1995. }
  1996. var kcpMaskToHeaderType = map[string]string{
  1997. "dns": "dns",
  1998. "dtls": "dtls",
  1999. "srtp": "srtp",
  2000. "utp": "utp",
  2001. "wechat": "wechat-video",
  2002. "wireguard": "wireguard",
  2003. }
  2004. var validFinalMaskUDPTypes = map[string]struct{}{
  2005. "salamander": {},
  2006. "mkcp-legacy": {},
  2007. "xdns": {},
  2008. "xicmp": {},
  2009. "noise": {},
  2010. "header-custom": {},
  2011. "realm": {},
  2012. }
  2013. var validFinalMaskTCPTypes = map[string]struct{}{
  2014. "header-custom": {},
  2015. "fragment": {},
  2016. "sudoku": {},
  2017. "xmc": {},
  2018. }
  2019. // applyKcpShareParams reconstructs legacy KCP share-link fields from either
  2020. // the historical kcpSettings.header/seed shape or the current finalmask model.
  2021. // This keeps subscription output compatible while avoiding panics when older
  2022. // keys are absent from modern inbounds.
  2023. func applyKcpShareParams(stream map[string]any, params map[string]string) {
  2024. extractKcpShareFields(stream).applyToParams(params)
  2025. }
  2026. func applyKcpShareObj(stream map[string]any, obj map[string]any) {
  2027. extractKcpShareFields(stream).applyToObj(obj)
  2028. }
  2029. type kcpShareFields struct {
  2030. headerType string
  2031. seed string
  2032. mtu int
  2033. tti int
  2034. }
  2035. func (f kcpShareFields) applyToParams(params map[string]string) {
  2036. if f.headerType != "" && f.headerType != "none" {
  2037. params["headerType"] = f.headerType
  2038. }
  2039. setStringParam(params, "seed", f.seed)
  2040. setIntParam(params, "mtu", f.mtu)
  2041. setIntParam(params, "tti", f.tti)
  2042. }
  2043. func (f kcpShareFields) applyToObj(obj map[string]any) {
  2044. if f.headerType != "" && f.headerType != "none" {
  2045. obj["type"] = f.headerType
  2046. }
  2047. setStringField(obj, "path", f.seed)
  2048. setIntField(obj, "mtu", f.mtu)
  2049. setIntField(obj, "tti", f.tti)
  2050. }
  2051. func extractKcpShareFields(stream map[string]any) kcpShareFields {
  2052. fields := kcpShareFields{headerType: "none"}
  2053. if kcp, ok := stream["kcpSettings"].(map[string]any); ok {
  2054. if header, ok := kcp["header"].(map[string]any); ok {
  2055. if value, ok := header["type"].(string); ok && value != "" {
  2056. fields.headerType = value
  2057. }
  2058. }
  2059. if value, ok := kcp["seed"].(string); ok && value != "" {
  2060. fields.seed = value
  2061. }
  2062. if value, ok := readPositiveInt(kcp["mtu"]); ok {
  2063. fields.mtu = value
  2064. }
  2065. if value, ok := readPositiveInt(kcp["tti"]); ok {
  2066. fields.tti = value
  2067. }
  2068. }
  2069. for _, rawMask := range normalizedFinalMaskUDPMasks(stream["finalmask"]) {
  2070. mask, _ := rawMask.(map[string]any)
  2071. if mask == nil {
  2072. continue
  2073. }
  2074. if maskType, _ := mask["type"].(string); maskType != "mkcp-legacy" {
  2075. continue
  2076. }
  2077. settings, _ := mask["settings"].(map[string]any)
  2078. header, _ := settings["header"].(string)
  2079. value, _ := settings["value"].(string)
  2080. if header == "" {
  2081. fields.seed = value
  2082. continue
  2083. }
  2084. if mapped, ok := kcpMaskToHeaderType[header]; ok {
  2085. fields.headerType = mapped
  2086. }
  2087. }
  2088. return fields
  2089. }
  2090. func readPositiveInt(value any) (int, bool) {
  2091. switch number := value.(type) {
  2092. case int:
  2093. return number, number > 0
  2094. case int32:
  2095. return int(number), number > 0
  2096. case int64:
  2097. return int(number), number > 0
  2098. case float32:
  2099. parsed := int(number)
  2100. return parsed, parsed > 0
  2101. case float64:
  2102. parsed := int(number)
  2103. return parsed, parsed > 0
  2104. default:
  2105. return 0, false
  2106. }
  2107. }
  2108. func setStringParam(params map[string]string, key, value string) {
  2109. if value == "" {
  2110. delete(params, key)
  2111. return
  2112. }
  2113. params[key] = value
  2114. }
  2115. func setIntParam(params map[string]string, key string, value int) {
  2116. if value <= 0 {
  2117. delete(params, key)
  2118. return
  2119. }
  2120. params[key] = fmt.Sprintf("%d", value)
  2121. }
  2122. func setStringField(obj map[string]any, key, value string) {
  2123. if value == "" {
  2124. delete(obj, key)
  2125. return
  2126. }
  2127. obj[key] = value
  2128. }
  2129. func setIntField(obj map[string]any, key string, value int) {
  2130. if value <= 0 {
  2131. delete(obj, key)
  2132. return
  2133. }
  2134. obj[key] = value
  2135. }
  2136. // applyFinalMaskParams exports the finalmask payload as the compact
  2137. // `fm=<json>` share-link field used by v2rayN-compatible clients.
  2138. func applyFinalMaskParams(finalmask map[string]any, params map[string]string) {
  2139. if fm, ok := marshalFinalMask(finalmask); ok {
  2140. params["fm"] = fm
  2141. }
  2142. }
  2143. func applyFinalMaskObj(finalmask map[string]any, obj map[string]any) {
  2144. if fm, ok := marshalFinalMask(finalmask); ok {
  2145. obj["fm"] = fm
  2146. }
  2147. }
  2148. func marshalFinalMask(finalmask map[string]any) (string, bool) {
  2149. normalized := normalizeFinalMask(finalmask)
  2150. if !hasFinalMaskContent(normalized) {
  2151. return "", false
  2152. }
  2153. b, err := json.Marshal(normalized)
  2154. if err != nil || len(b) == 0 || string(b) == "null" {
  2155. return "", false
  2156. }
  2157. return string(b), true
  2158. }
  2159. func normalizeFinalMask(finalmask map[string]any) map[string]any {
  2160. tcpMasks := normalizedFinalMaskTCPMasks(finalmask)
  2161. udpMasks := normalizedFinalMaskUDPMasks(finalmask)
  2162. quicParams, hasQuicParams := finalmask["quicParams"].(map[string]any)
  2163. if len(tcpMasks) == 0 && len(udpMasks) == 0 && !hasQuicParams {
  2164. return nil
  2165. }
  2166. result := map[string]any{}
  2167. if len(tcpMasks) > 0 {
  2168. result["tcp"] = tcpMasks
  2169. }
  2170. if len(udpMasks) > 0 {
  2171. result["udp"] = udpMasks
  2172. }
  2173. if hasQuicParams && len(quicParams) > 0 {
  2174. result["quicParams"] = quicParams
  2175. }
  2176. return result
  2177. }
  2178. func normalizedFinalMaskTCPMasks(value any) []any {
  2179. finalmask, _ := value.(map[string]any)
  2180. if finalmask == nil {
  2181. return nil
  2182. }
  2183. rawMasks, _ := finalmask["tcp"].([]any)
  2184. if len(rawMasks) == 0 {
  2185. return nil
  2186. }
  2187. normalized := make([]any, 0, len(rawMasks))
  2188. for _, rawMask := range rawMasks {
  2189. mask, _ := rawMask.(map[string]any)
  2190. if mask == nil {
  2191. continue
  2192. }
  2193. maskType, _ := mask["type"].(string)
  2194. if _, ok := validFinalMaskTCPTypes[maskType]; !ok || maskType == "" {
  2195. continue
  2196. }
  2197. normalizedMask := map[string]any{"type": maskType}
  2198. if settings, ok := mask["settings"].(map[string]any); ok && len(settings) > 0 {
  2199. normalizedMask["settings"] = settings
  2200. }
  2201. normalized = append(normalized, normalizedMask)
  2202. }
  2203. if len(normalized) == 0 {
  2204. return nil
  2205. }
  2206. return normalized
  2207. }
  2208. func normalizedFinalMaskUDPMasks(value any) []any {
  2209. finalmask, _ := value.(map[string]any)
  2210. if finalmask == nil {
  2211. return nil
  2212. }
  2213. rawMasks, _ := finalmask["udp"].([]any)
  2214. if len(rawMasks) == 0 {
  2215. return nil
  2216. }
  2217. normalized := make([]any, 0, len(rawMasks))
  2218. for _, rawMask := range rawMasks {
  2219. mask, _ := rawMask.(map[string]any)
  2220. if mask == nil {
  2221. continue
  2222. }
  2223. maskType, _ := mask["type"].(string)
  2224. if _, ok := validFinalMaskUDPTypes[maskType]; !ok || maskType == "" {
  2225. continue
  2226. }
  2227. normalizedMask := map[string]any{"type": maskType}
  2228. if settings, ok := mask["settings"].(map[string]any); ok && len(settings) > 0 {
  2229. normalizedMask["settings"] = settings
  2230. }
  2231. normalized = append(normalized, normalizedMask)
  2232. }
  2233. if len(normalized) == 0 {
  2234. return nil
  2235. }
  2236. return normalized
  2237. }
  2238. func hasFinalMaskContent(value any) bool {
  2239. switch v := value.(type) {
  2240. case nil:
  2241. return false
  2242. case string:
  2243. return len(v) > 0
  2244. case map[string]any:
  2245. for _, item := range v {
  2246. if hasFinalMaskContent(item) {
  2247. return true
  2248. }
  2249. }
  2250. return false
  2251. case []any:
  2252. return slices.ContainsFunc(v, hasFinalMaskContent)
  2253. default:
  2254. return true
  2255. }
  2256. }
  2257. func searchHost(headers any) string {
  2258. data, _ := headers.(map[string]any)
  2259. for k, v := range data {
  2260. if strings.EqualFold(k, "host") {
  2261. switch v.(type) {
  2262. case []any:
  2263. hosts, _ := v.([]any)
  2264. if len(hosts) > 0 {
  2265. h, _ := hosts[0].(string)
  2266. return h
  2267. }
  2268. return ""
  2269. case any:
  2270. h, _ := v.(string)
  2271. return h
  2272. }
  2273. }
  2274. }
  2275. return ""
  2276. }
  2277. // PageData is a view model for subpage.html
  2278. // PageData contains data for rendering the subscription information page.
  2279. type PageData struct {
  2280. Host string
  2281. BasePath string
  2282. SId string
  2283. Enabled bool
  2284. Download string
  2285. Upload string
  2286. Total string
  2287. Used string
  2288. Remained string
  2289. Expire int64
  2290. LastOnline int64
  2291. Datepicker string
  2292. DownloadByte int64
  2293. UploadByte int64
  2294. TotalByte int64
  2295. SubUrl string
  2296. SubJsonUrl string
  2297. SubClashUrl string
  2298. SubTitle string
  2299. SubSupportUrl string
  2300. Result []string
  2301. Emails []string
  2302. }
  2303. // ResolveRequest extracts scheme and host info from request/headers consistently.
  2304. // ResolveRequest extracts scheme, host, and header information from an HTTP request.
  2305. func (s *SubService) ResolveRequest(c *gin.Context) (scheme string, host string, hostWithPort string, hostHeader string) {
  2306. // scheme
  2307. scheme = "http"
  2308. if c.Request.TLS != nil || strings.EqualFold(c.GetHeader("X-Forwarded-Proto"), "https") {
  2309. scheme = "https"
  2310. }
  2311. // base host (no port)
  2312. if h, err := getHostFromXFH(c.GetHeader("X-Forwarded-Host")); err == nil && h != "" {
  2313. host = h
  2314. }
  2315. if host == "" {
  2316. host = c.GetHeader("X-Real-IP")
  2317. }
  2318. if host == "" {
  2319. var err error
  2320. host, _, err = net.SplitHostPort(c.Request.Host)
  2321. if err != nil {
  2322. host = c.Request.Host
  2323. }
  2324. }
  2325. // host:port for URLs
  2326. hostWithPort = c.GetHeader("X-Forwarded-Host")
  2327. if hostWithPort == "" {
  2328. hostWithPort = c.Request.Host
  2329. }
  2330. if hostWithPort == "" {
  2331. hostWithPort = host
  2332. }
  2333. // header display host
  2334. hostHeader = c.GetHeader("X-Forwarded-Host")
  2335. if hostHeader == "" {
  2336. hostHeader = c.GetHeader("X-Real-IP")
  2337. }
  2338. if hostHeader == "" {
  2339. hostHeader = host
  2340. }
  2341. return
  2342. }
  2343. // BuildURLs constructs absolute subscription and JSON subscription URLs for a given subscription ID.
  2344. // It prioritizes configured URIs, then individual settings, and finally falls back to request-derived components.
  2345. func (s *SubService) BuildURLs(subPath, subJsonPath, subClashPath, subId string) (subURL, subJsonURL, subClashURL string) {
  2346. if subId == "" {
  2347. return "", "", ""
  2348. }
  2349. configuredSubURI, _ := s.settingService.GetSubURI()
  2350. configuredSubJsonURI, _ := s.settingService.GetSubJsonURI()
  2351. configuredSubClashURI, _ := s.settingService.GetSubClashURI()
  2352. // Same base as the panel's Client Information page; s.address is the
  2353. // subscriber's host already normalized away from any loopback/bind IP.
  2354. base := s.settingService.BuildSubURIBase(s.address)
  2355. subURL = s.buildSingleURL(configuredSubURI, base, subPath, subId)
  2356. // When subURI is explicitly configured (reverse-proxy setup), use its
  2357. // scheme+host as the base for JSON and Clash URLs so they match the
  2358. // reverse-proxy endpoint instead of the raw sub-server port. Fall back
  2359. // to the request-derived base if subURI is empty or can't be parsed
  2360. // into a scheme+host (e.g. a malformed value with no scheme).
  2361. jsonClashBase := base
  2362. if configuredSubURI != "" {
  2363. if derived := s.extractBaseFromURI(configuredSubURI); derived != "" {
  2364. jsonClashBase = derived
  2365. }
  2366. }
  2367. subJsonURL = s.buildSingleURL(configuredSubJsonURI, jsonClashBase, subJsonPath, subId)
  2368. subClashURL = s.buildSingleURL(configuredSubClashURI, jsonClashBase, subClashPath, subId)
  2369. return subURL, subJsonURL, subClashURL
  2370. }
  2371. // extractBaseFromURI extracts scheme://host from a configured URI.
  2372. // e.g., "https://example.com/sub-xxx/" → "https://example.com".
  2373. // Returns "" when the URI is empty or lacks a scheme/host, so callers can
  2374. // fall back to the request-derived base instead of emitting a broken value.
  2375. func (s *SubService) extractBaseFromURI(uri string) string {
  2376. u, err := url.Parse(uri)
  2377. if err != nil || u.Scheme == "" || u.Host == "" {
  2378. return ""
  2379. }
  2380. return fmt.Sprintf("%s://%s", u.Scheme, u.Host)
  2381. }
  2382. // buildSingleURL constructs a single URL using configured URI or base components
  2383. func (s *SubService) buildSingleURL(configuredURI, base, basePath, subId string) string {
  2384. if configuredURI != "" {
  2385. return s.joinPathWithID(configuredURI, subId)
  2386. }
  2387. return s.joinPathWithID(base+basePath, subId)
  2388. }
  2389. // joinPathWithID safely joins a base path with a subscription ID
  2390. func (s *SubService) joinPathWithID(basePath, subId string) string {
  2391. if strings.HasSuffix(basePath, "/") {
  2392. return basePath + subId
  2393. }
  2394. return basePath + "/" + subId
  2395. }
  2396. // BuildPageData parses header and prepares the template view model.
  2397. // BuildPageData constructs page data for rendering the subscription information page.
  2398. func (s *SubService) BuildPageData(subId string, hostHeader string, traffic xray.ClientTraffic, lastOnline int64, subs []string, emails []string, subURL, subJsonURL, subClashURL string, basePath string, subTitle string, subSupportUrl string) PageData {
  2399. download := common.FormatTraffic(traffic.Down)
  2400. upload := common.FormatTraffic(traffic.Up)
  2401. total := "∞"
  2402. used := common.FormatTraffic(traffic.Up + traffic.Down)
  2403. remained := ""
  2404. if traffic.Total > 0 {
  2405. total = common.FormatTraffic(traffic.Total)
  2406. left := max(traffic.Total-(traffic.Up+traffic.Down), 0)
  2407. remained = common.FormatTraffic(left)
  2408. }
  2409. datepicker := s.datepicker
  2410. if datepicker == "" {
  2411. datepicker = "gregorian"
  2412. }
  2413. pageLinks := make([]string, 0, len(subs))
  2414. pageEmails := make([]string, 0, len(subs))
  2415. for i, sub := range subs {
  2416. email := ""
  2417. if i < len(emails) {
  2418. email = emails[i]
  2419. }
  2420. for _, link := range splitLinkLines(sub) {
  2421. pageLinks = append(pageLinks, link)
  2422. pageEmails = append(pageEmails, email)
  2423. }
  2424. }
  2425. return PageData{
  2426. Host: hostHeader,
  2427. BasePath: basePath,
  2428. SId: subId,
  2429. Enabled: traffic.Enable,
  2430. Download: download,
  2431. Upload: upload,
  2432. Total: total,
  2433. Used: used,
  2434. Remained: remained,
  2435. Expire: traffic.ExpiryTime / 1000,
  2436. LastOnline: lastOnline,
  2437. Datepicker: datepicker,
  2438. DownloadByte: traffic.Down,
  2439. UploadByte: traffic.Up,
  2440. TotalByte: traffic.Total,
  2441. SubUrl: subURL,
  2442. SubJsonUrl: subJsonURL,
  2443. SubClashUrl: subClashURL,
  2444. SubTitle: subTitle,
  2445. SubSupportUrl: subSupportUrl,
  2446. Result: pageLinks,
  2447. Emails: pageEmails,
  2448. }
  2449. }
  2450. func getHostFromXFH(s string) (string, error) {
  2451. if strings.Contains(s, ":") {
  2452. realHost, _, err := net.SplitHostPort(s)
  2453. if err != nil {
  2454. return "", err
  2455. }
  2456. return realHost, nil
  2457. }
  2458. return s, nil
  2459. }