endpoint.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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. // advertisedEndpoints is every endpoint a stream-less link (mtproto, wireguard,
  53. // amneziawg) must fan out over: the externalProxy/Host entries, else the default.
  54. func (s *SubService) advertisedEndpoints(inbound *model.Inbound) []ShareEndpoint {
  55. stream := unmarshalStreamSettings(inbound.StreamSettings)
  56. if externalProxies, ok := stream["externalProxy"].([]any); ok {
  57. endpoints := make([]ShareEndpoint, 0, len(externalProxies))
  58. for _, raw := range externalProxies {
  59. if ep, ok := raw.(map[string]any); ok {
  60. endpoints = append(endpoints, externalProxyToEndpoint(ep))
  61. }
  62. }
  63. if len(endpoints) > 0 {
  64. return endpoints
  65. }
  66. }
  67. return []ShareEndpoint{s.inboundDefaultEndpoint(inbound)}
  68. }
  69. // applyEndpointTLSParams applies an endpoint's TLS overrides onto a URL-param
  70. // map. External-proxy endpoints delegate to the unchanged helper; host/default
  71. // endpoints carry no override yet (Phase 4).
  72. func applyEndpointTLSParams(e ShareEndpoint, params map[string]string, security string) {
  73. if e.ep != nil {
  74. applyExternalProxyTLSParams(e.ep, params, security)
  75. }
  76. }
  77. // applyEndpointTLSObj is applyEndpointTLSParams for the VMess base64-JSON form.
  78. func applyEndpointTLSObj(e ShareEndpoint, obj map[string]any, security string) {
  79. if e.ep != nil {
  80. applyExternalProxyTLSObj(e.ep, obj, security)
  81. }
  82. }
  83. // dropBaseRealityParams removes the parameters that only mean something on a
  84. // reality link once a host forces the endpoint to plain TLS or no TLS.
  85. func dropBaseRealityParams(params map[string]string, baseSecurity, securityToApply string) {
  86. if baseSecurity != "reality" || securityToApply == "reality" {
  87. return
  88. }
  89. // sni and fp name the master's reality dest, not this endpoint's own
  90. // certificate; the host's values are re-applied right after this.
  91. for _, k := range []string{"pbk", "sid", "spx", "pqv", "sni", "fp"} {
  92. delete(params, k)
  93. }
  94. }
  95. // buildEndpointLinks renders one URL-param link per endpoint (vless/trojan/ss).
  96. // securityToApply mirrors the legacy externalProxy loop: "same" keeps the base
  97. // security, otherwise the endpoint's forceTls wins; "none" strips TLS hint
  98. // fields at emit time.
  99. func (s *SubService) buildEndpointLinks(
  100. eps []ShareEndpoint,
  101. params map[string]string,
  102. baseSecurity string,
  103. makeLink func(e ShareEndpoint) string,
  104. makeRemark func(e ShareEndpoint) string,
  105. ) string {
  106. links := make([]string, 0, len(eps))
  107. for _, e := range eps {
  108. securityToApply := baseSecurity
  109. if e.ForceTls != "same" {
  110. securityToApply = e.ForceTls
  111. }
  112. nextParams := cloneStringMap(params)
  113. dropBaseRealityParams(nextParams, baseSecurity, securityToApply)
  114. applyEndpointTLSParams(e, nextParams, securityToApply)
  115. applyEndpointRealityParams(e, nextParams, securityToApply)
  116. applyEndpointHostPath(e, nextParams)
  117. applyEndpointFinalMask(e, nextParams)
  118. applyEndpointAllowInsecure(e, nextParams, securityToApply)
  119. remark := makeRemark(e)
  120. if e.ServerDescription != "" {
  121. remark = appendHappServerDescription(remark, e.ServerDescription)
  122. }
  123. links = append(links, buildLinkWithParamsAndSecurity(
  124. makeLink(e),
  125. nextParams,
  126. remark,
  127. securityToApply,
  128. e.ForceTls == "none",
  129. ))
  130. }
  131. return strings.Join(links, "\n")
  132. }
  133. func appendHappServerDescription(remark, desc string) string {
  134. if desc == "" {
  135. return remark
  136. }
  137. encoded := base64.StdEncoding.EncodeToString([]byte(desc))
  138. return remark + "?serverDescription=" + encoded
  139. }
  140. // buildEndpointVmessLinks renders one VMess base64-JSON link per endpoint.
  141. func (s *SubService) buildEndpointVmessLinks(eps []ShareEndpoint, baseObj map[string]any, inbound *model.Inbound, email string, transport string) string {
  142. var links strings.Builder
  143. for index, e := range eps {
  144. securityToApply, _ := baseObj["tls"].(string)
  145. if e.ForceTls != "same" {
  146. securityToApply = e.ForceTls
  147. }
  148. newObj := cloneVmessShareObj(baseObj, e.ForceTls)
  149. newObj["ps"] = s.endpointRemark(inbound, email, e.ep, transport)
  150. newObj["add"] = e.Address
  151. newObj["port"] = e.Port
  152. if e.ForceTls != "same" {
  153. newObj["tls"] = e.ForceTls
  154. }
  155. if e.ServerDescription != "" {
  156. newObj["serverDescription"] = e.ServerDescription
  157. }
  158. applyEndpointTLSObj(e, newObj, securityToApply)
  159. applyEndpointHostPathObj(e, newObj)
  160. applyEndpointFinalMaskObj(e, newObj)
  161. if index > 0 {
  162. links.WriteString("\n")
  163. }
  164. links.WriteString(buildVmessLink(newObj))
  165. }
  166. return links.String()
  167. }