1
0

service.go 68 KB

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