endpoint.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. package sub
  2. import (
  3. "encoding/base64"
  4. "strings"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  6. )
  7. // ShareEndpoint is one render target for a subscription link: the address/port
  8. // to dial plus an optional set of TLS overrides. It unifies two sources behind
  9. // one type so the per-protocol link builders don't branch on where the override
  10. // came from:
  11. //
  12. // - a legacy externalProxy entry (Phase 1): the source map is carried in `ep`
  13. // and applied through the unchanged applyExternalProxyTLS* helpers, so the
  14. // emitted link is byte-identical to the pre-refactor output;
  15. // - a Host row (Phase 4): leaves `ep` nil and uses typed override fields.
  16. //
  17. // ForceTls is the verbatim "same"/"tls"/"none"/"" value — never pre-resolved,
  18. // because three behaviors branch on the raw string (keep-base, obj["tls"]
  19. // rewrite, none-strip).
  20. type ShareEndpoint struct {
  21. Address string
  22. Port int
  23. Remark string // extra remark slot fed to genRemark, not a rendered remark
  24. ServerDescription string // subtitle caption displayed in Happ client
  25. ForceTls string
  26. // ep is the source externalProxy entry. nil for host/default endpoints.
  27. ep map[string]any
  28. }
  29. // externalProxyToEndpoint maps one externalProxy entry to an endpoint that
  30. // carries the entry for delegated, provably-identical TLS application.
  31. func externalProxyToEndpoint(ep map[string]any) ShareEndpoint {
  32. e := ShareEndpoint{ep: ep}
  33. e.Address, _ = ep["dest"].(string)
  34. if p, ok := ep["port"].(float64); ok {
  35. e.Port = int(p)
  36. }
  37. e.Remark, _ = ep["remark"].(string)
  38. e.ServerDescription, _ = ep["serverDescription"].(string)
  39. e.ForceTls, _ = ep["forceTls"].(string)
  40. return e
  41. }
  42. // inboundDefaultEndpoint is the endpoint for an inbound's own resolved
  43. // address/port (the no-externalProxy default). forceTls "same" keeps the base
  44. // security; no per-endpoint TLS override.
  45. func (s *SubService) inboundDefaultEndpoint(inbound *model.Inbound) ShareEndpoint {
  46. return ShareEndpoint{
  47. Address: s.resolveInboundAddress(inbound),
  48. Port: inbound.Port,
  49. ForceTls: "same",
  50. }
  51. }
  52. // applyEndpointTLSParams applies an endpoint's TLS overrides onto a URL-param
  53. // map. External-proxy endpoints delegate to the unchanged helper; host/default
  54. // endpoints carry no override yet (Phase 4).
  55. func applyEndpointTLSParams(e ShareEndpoint, params map[string]string, security string) {
  56. if e.ep != nil {
  57. applyExternalProxyTLSParams(e.ep, params, security)
  58. }
  59. }
  60. // applyEndpointTLSObj is applyEndpointTLSParams for the VMess base64-JSON form.
  61. func applyEndpointTLSObj(e ShareEndpoint, obj map[string]any, security string) {
  62. if e.ep != nil {
  63. applyExternalProxyTLSObj(e.ep, obj, security)
  64. }
  65. }
  66. // dropBaseRealityParams removes the parameters that only mean something on a
  67. // reality link once a host forces the endpoint to plain TLS or no TLS.
  68. func dropBaseRealityParams(params map[string]string, baseSecurity, securityToApply string) {
  69. if baseSecurity != "reality" || securityToApply == "reality" {
  70. return
  71. }
  72. // sni and fp name the master's reality dest, not this endpoint's own
  73. // certificate; the host's values are re-applied right after this.
  74. for _, k := range []string{"pbk", "sid", "spx", "pqv", "sni", "fp"} {
  75. delete(params, k)
  76. }
  77. }
  78. // buildEndpointLinks renders one URL-param link per endpoint (vless/trojan/ss).
  79. // securityToApply mirrors the legacy externalProxy loop: "same" keeps the base
  80. // security, otherwise the endpoint's forceTls wins; "none" strips TLS hint
  81. // fields at emit time.
  82. func (s *SubService) buildEndpointLinks(
  83. eps []ShareEndpoint,
  84. params map[string]string,
  85. baseSecurity string,
  86. makeLink func(e ShareEndpoint) string,
  87. makeRemark func(e ShareEndpoint) string,
  88. ) string {
  89. links := make([]string, 0, len(eps))
  90. for _, e := range eps {
  91. securityToApply := baseSecurity
  92. if e.ForceTls != "same" {
  93. securityToApply = e.ForceTls
  94. }
  95. nextParams := cloneStringMap(params)
  96. dropBaseRealityParams(nextParams, baseSecurity, securityToApply)
  97. applyEndpointTLSParams(e, nextParams, securityToApply)
  98. applyEndpointRealityParams(e, nextParams, securityToApply)
  99. applyEndpointHostPath(e, nextParams)
  100. applyEndpointFinalMask(e, nextParams)
  101. applyEndpointAllowInsecure(e, nextParams, securityToApply)
  102. remark := makeRemark(e)
  103. if e.ServerDescription != "" {
  104. remark = appendHappServerDescription(remark, e.ServerDescription)
  105. }
  106. links = append(links, buildLinkWithParamsAndSecurity(
  107. makeLink(e),
  108. nextParams,
  109. remark,
  110. securityToApply,
  111. e.ForceTls == "none",
  112. ))
  113. }
  114. return strings.Join(links, "\n")
  115. }
  116. func appendHappServerDescription(remark, desc string) string {
  117. if desc == "" {
  118. return remark
  119. }
  120. encoded := base64.StdEncoding.EncodeToString([]byte(desc))
  121. return remark + "?serverDescription=" + encoded
  122. }
  123. // buildEndpointVmessLinks renders one VMess base64-JSON link per endpoint.
  124. func (s *SubService) buildEndpointVmessLinks(eps []ShareEndpoint, baseObj map[string]any, inbound *model.Inbound, email string, transport string) string {
  125. var links strings.Builder
  126. for index, e := range eps {
  127. securityToApply, _ := baseObj["tls"].(string)
  128. if e.ForceTls != "same" {
  129. securityToApply = e.ForceTls
  130. }
  131. newObj := cloneVmessShareObj(baseObj, e.ForceTls)
  132. newObj["ps"] = s.endpointRemark(inbound, email, e.ep, transport)
  133. newObj["add"] = e.Address
  134. newObj["port"] = e.Port
  135. if e.ForceTls != "same" {
  136. newObj["tls"] = e.ForceTls
  137. }
  138. if e.ServerDescription != "" {
  139. newObj["serverDescription"] = e.ServerDescription
  140. }
  141. applyEndpointTLSObj(e, newObj, securityToApply)
  142. applyEndpointHostPathObj(e, newObj)
  143. applyEndpointFinalMaskObj(e, newObj)
  144. if index > 0 {
  145. links.WriteString("\n")
  146. }
  147. links.WriteString(buildVmessLink(newObj))
  148. }
  149. return links.String()
  150. }