host.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. package service
  2. import (
  3. "slices"
  4. "sort"
  5. "strconv"
  6. "strings"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database"
  8. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  9. "github.com/mhsanaei/3x-ui/v3/internal/util/common"
  10. "github.com/mhsanaei/3x-ui/v3/internal/util/random"
  11. "github.com/mhsanaei/3x-ui/v3/internal/web/entity"
  12. "gorm.io/gorm"
  13. )
  14. type HostService struct{}
  15. func formatHostAddr(addr string, port int) string {
  16. if port <= 0 {
  17. return addr
  18. }
  19. if strings.Contains(addr, ":") {
  20. return "[" + addr + "]:" + strconv.Itoa(port)
  21. }
  22. return addr + ":" + strconv.Itoa(port)
  23. }
  24. func newHostGroup(h *model.Host, groupId string) *entity.HostGroup {
  25. return &entity.HostGroup{
  26. GroupId: groupId,
  27. InboundIds: []int{},
  28. Hosts: []string{},
  29. SortOrder: h.SortOrder,
  30. Remark: h.Remark,
  31. ServerDescription: h.ServerDescription,
  32. IsDisabled: h.IsDisabled,
  33. IsHidden: h.IsHidden,
  34. Tags: h.Tags,
  35. Port: h.Port,
  36. Security: h.Security,
  37. Sni: h.Sni,
  38. HostHeader: h.HostHeader,
  39. Path: h.Path,
  40. Alpn: h.Alpn,
  41. Fingerprint: h.Fingerprint,
  42. CipherSuites: h.CipherSuites,
  43. OverrideSniFromAddress: h.OverrideSniFromAddress,
  44. KeepSniBlank: h.KeepSniBlank,
  45. PinnedPeerCertSha256: h.PinnedPeerCertSha256,
  46. VerifyPeerCertByName: h.VerifyPeerCertByName,
  47. AllowInsecure: h.AllowInsecure,
  48. EchConfigList: h.EchConfigList,
  49. MuxParams: h.MuxParams,
  50. SockoptParams: h.SockoptParams,
  51. FinalMask: h.FinalMask,
  52. VlessRoute: h.VlessRoute,
  53. ExcludeFromSubTypes: h.ExcludeFromSubTypes,
  54. NodeGuids: h.NodeGuids,
  55. MihomoIpVersion: h.MihomoIpVersion,
  56. MihomoX25519: h.MihomoX25519,
  57. ShuffleHost: h.ShuffleHost,
  58. }
  59. }
  60. func groupHosts(hosts []*model.Host) []*entity.HostGroup {
  61. groupsMap := make(map[string]*entity.HostGroup)
  62. var orderedGroupIds []string
  63. for _, h := range hosts {
  64. gId := h.GroupId
  65. if gId == "" {
  66. gId = "fallback_" + strconv.Itoa(h.Id)
  67. }
  68. g, exists := groupsMap[gId]
  69. if !exists {
  70. g = newHostGroup(h, gId)
  71. groupsMap[gId] = g
  72. orderedGroupIds = append(orderedGroupIds, gId)
  73. }
  74. if !slices.Contains(g.InboundIds, h.InboundId) {
  75. g.InboundIds = append(g.InboundIds, h.InboundId)
  76. }
  77. hostStr := formatHostAddr(h.Address, h.Port)
  78. if !slices.Contains(g.Hosts, hostStr) {
  79. g.Hosts = append(g.Hosts, hostStr)
  80. }
  81. if h.SortOrder < g.SortOrder {
  82. g.SortOrder = h.SortOrder
  83. }
  84. }
  85. res := make([]*entity.HostGroup, 0, len(orderedGroupIds))
  86. for _, gId := range orderedGroupIds {
  87. res = append(res, groupsMap[gId])
  88. }
  89. sort.SliceStable(res, func(i, j int) bool {
  90. if res[i].SortOrder != res[j].SortOrder {
  91. return res[i].SortOrder < res[j].SortOrder
  92. }
  93. return res[i].Remark < res[j].Remark
  94. })
  95. return res
  96. }
  97. func buildHostRows(groupId string, req *entity.HostGroup) []*model.Host {
  98. hostsToProcess := req.Hosts
  99. if len(hostsToProcess) == 0 {
  100. hostsToProcess = []string{""}
  101. }
  102. var rows []*model.Host
  103. for _, hostStr := range hostsToProcess {
  104. addr, port := parseHostAndPort(hostStr, req.Port)
  105. for _, inboundId := range req.InboundIds {
  106. rows = append(rows, &model.Host{
  107. GroupId: groupId,
  108. InboundId: inboundId,
  109. SortOrder: req.SortOrder,
  110. Remark: req.Remark,
  111. ServerDescription: req.ServerDescription,
  112. IsDisabled: req.IsDisabled,
  113. IsHidden: req.IsHidden,
  114. Tags: req.Tags,
  115. Address: addr,
  116. Port: port,
  117. Security: req.Security,
  118. Sni: req.Sni,
  119. HostHeader: req.HostHeader,
  120. Path: req.Path,
  121. Alpn: req.Alpn,
  122. Fingerprint: req.Fingerprint,
  123. CipherSuites: req.CipherSuites,
  124. OverrideSniFromAddress: req.OverrideSniFromAddress,
  125. KeepSniBlank: req.KeepSniBlank,
  126. PinnedPeerCertSha256: req.PinnedPeerCertSha256,
  127. VerifyPeerCertByName: req.VerifyPeerCertByName,
  128. AllowInsecure: req.AllowInsecure,
  129. EchConfigList: req.EchConfigList,
  130. MuxParams: req.MuxParams,
  131. SockoptParams: req.SockoptParams,
  132. FinalMask: req.FinalMask,
  133. VlessRoute: req.VlessRoute,
  134. ExcludeFromSubTypes: req.ExcludeFromSubTypes,
  135. NodeGuids: req.NodeGuids,
  136. MihomoIpVersion: req.MihomoIpVersion,
  137. MihomoX25519: req.MihomoX25519,
  138. ShuffleHost: req.ShuffleHost,
  139. })
  140. }
  141. }
  142. return rows
  143. }
  144. // adoptedHostRows projects a node's host groups onto a freshly adopted central
  145. // inbound so TLS/SNI/fingerprint overrides survive the node-to-master import.
  146. func adoptedHostRows(groups []*entity.HostGroup, nodeInboundId, centralInboundId int) []*model.Host {
  147. var rows []*model.Host
  148. for _, g := range groups {
  149. if g == nil || !slices.Contains(g.InboundIds, nodeInboundId) {
  150. continue
  151. }
  152. scoped := *g
  153. scoped.InboundIds = []int{centralInboundId}
  154. rows = append(rows, buildHostRows(g.GroupId, &scoped)...)
  155. }
  156. return rows
  157. }
  158. func validateInboundsExist(tx *gorm.DB, inboundIds []int) error {
  159. for _, inboundId := range inboundIds {
  160. var count int64
  161. if err := tx.Model(&model.Inbound{}).Where("id = ?", inboundId).Count(&count).Error; err != nil {
  162. return err
  163. }
  164. if count == 0 {
  165. return common.NewError("inbound not found")
  166. }
  167. }
  168. return nil
  169. }
  170. func (s *HostService) GetHosts() ([]*entity.HostGroup, error) {
  171. var hosts []*model.Host
  172. err := database.GetDB().Order("inbound_id asc, sort_order asc, id asc").Find(&hosts).Error
  173. if err != nil {
  174. return nil, err
  175. }
  176. return groupHosts(hosts), nil
  177. }
  178. func (s *HostService) GetHostsByInbound(inboundId int) ([]*entity.HostGroup, error) {
  179. var groupIds []string
  180. if err := database.GetDB().Model(&model.Host{}).Where("inbound_id = ?", inboundId).Distinct().Pluck("group_id", &groupIds).Error; err != nil {
  181. return nil, err
  182. }
  183. if len(groupIds) == 0 {
  184. return nil, nil
  185. }
  186. var hosts []*model.Host
  187. if err := database.GetDB().Where("group_id IN ?", groupIds).Order("sort_order asc, id asc").Find(&hosts).Error; err != nil {
  188. return nil, err
  189. }
  190. return groupHosts(hosts), nil
  191. }
  192. func (s *HostService) GetHostGroup(groupId string) (*entity.HostGroup, error) {
  193. var hosts []*model.Host
  194. err := database.GetDB().Where("group_id = ?", groupId).Order("sort_order asc, id asc").Find(&hosts).Error
  195. if err != nil {
  196. return nil, err
  197. }
  198. if len(hosts) == 0 {
  199. return nil, common.NewError("host not found")
  200. }
  201. grouped := groupHosts(hosts)
  202. if len(grouped) == 0 {
  203. return nil, common.NewError("host not found")
  204. }
  205. return grouped[0], nil
  206. }
  207. func (s *HostService) AddHostGroup(req *entity.HostGroup) ([]*model.Host, error) {
  208. groupId := req.GroupId
  209. if groupId == "" {
  210. groupId = random.NumLower(16)
  211. }
  212. created := buildHostRows(groupId, req)
  213. err := database.GetDB().Transaction(func(tx *gorm.DB) error {
  214. if err := validateInboundsExist(tx, req.InboundIds); err != nil {
  215. return err
  216. }
  217. if len(created) > 0 {
  218. return tx.Create(&created).Error
  219. }
  220. return nil
  221. })
  222. if err != nil {
  223. return nil, err
  224. }
  225. return created, nil
  226. }
  227. func (s *HostService) UpdateHostGroup(groupId string, req *entity.HostGroup) ([]*model.Host, error) {
  228. created := buildHostRows(groupId, req)
  229. err := database.GetDB().Transaction(func(tx *gorm.DB) error {
  230. var count int64
  231. if err := tx.Model(&model.Host{}).Where("group_id = ?", groupId).Count(&count).Error; err != nil {
  232. return err
  233. }
  234. if count == 0 {
  235. return common.NewError("host not found")
  236. }
  237. if err := validateInboundsExist(tx, req.InboundIds); err != nil {
  238. return err
  239. }
  240. if err := tx.Where("group_id = ?", groupId).Delete(&model.Host{}).Error; err != nil {
  241. return err
  242. }
  243. if len(created) > 0 {
  244. return tx.Create(&created).Error
  245. }
  246. return nil
  247. })
  248. if err != nil {
  249. return nil, err
  250. }
  251. return created, nil
  252. }
  253. func (s *HostService) DeleteHostGroup(groupId string) error {
  254. return database.GetDB().Where("group_id = ?", groupId).Delete(&model.Host{}).Error
  255. }
  256. func (s *HostService) SetHostGroupEnable(groupId string, enable bool) error {
  257. return database.GetDB().Model(&model.Host{}).Where("group_id = ?", groupId).Update("is_disabled", !enable).Error
  258. }
  259. func (s *HostService) SetHostsGroupEnable(groupIds []string, enable bool) error {
  260. if len(groupIds) == 0 {
  261. return nil
  262. }
  263. return database.GetDB().Model(&model.Host{}).Where("group_id IN ?", groupIds).Update("is_disabled", !enable).Error
  264. }
  265. func (s *HostService) DeleteHostsGroup(groupIds []string) error {
  266. if len(groupIds) == 0 {
  267. return nil
  268. }
  269. return database.GetDB().Where("group_id IN ?", groupIds).Delete(&model.Host{}).Error
  270. }
  271. func (s *HostService) ReorderHostGroups(groupIds []string) error {
  272. if len(groupIds) == 0 {
  273. return nil
  274. }
  275. return database.GetDB().Transaction(func(tx *gorm.DB) error {
  276. for i, groupId := range groupIds {
  277. if err := tx.Model(&model.Host{}).Where("group_id = ?", groupId).Update("sort_order", i).Error; err != nil {
  278. return err
  279. }
  280. }
  281. return nil
  282. })
  283. }
  284. func (s *HostService) GetAllTags() ([]string, error) {
  285. var hosts []*model.Host
  286. err := database.GetDB().Find(&hosts).Error
  287. if err != nil {
  288. return nil, err
  289. }
  290. set := make(map[string]struct{})
  291. for _, h := range hosts {
  292. for _, tag := range h.Tags {
  293. if tag != "" {
  294. set[tag] = struct{}{}
  295. }
  296. }
  297. }
  298. out := make([]string, 0, len(set))
  299. for tag := range set {
  300. out = append(out, tag)
  301. }
  302. sort.Strings(out)
  303. return out, nil
  304. }
  305. func parseHostAndPort(hostStr string, defaultPort int) (string, int) {
  306. hostStr = strings.TrimSpace(hostStr)
  307. if hostStr == "" {
  308. return "", defaultPort
  309. }
  310. if strings.Count(hostStr, ":") > 1 && !strings.Contains(hostStr, "[") {
  311. return hostStr, defaultPort
  312. }
  313. lastColon := strings.LastIndex(hostStr, ":")
  314. if lastColon != -1 && lastColon < len(hostStr)-1 {
  315. pStr := hostStr[lastColon+1:]
  316. if p, err := strconv.Atoi(pStr); err == nil && p >= 0 && p <= 65535 {
  317. addr := hostStr[:lastColon]
  318. if strings.HasPrefix(addr, "[") && strings.HasSuffix(addr, "]") {
  319. addr = addr[1 : len(addr)-1]
  320. }
  321. return addr, p
  322. }
  323. }
  324. addr := hostStr
  325. if strings.HasPrefix(addr, "[") && strings.HasSuffix(addr, "]") {
  326. addr = addr[1 : len(addr)-1]
  327. }
  328. return addr, defaultPort
  329. }