1
0

service.go 94 KB

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