service.go 103 KB

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