service.go 69 KB

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