Skip to content

Commit

Permalink
[feature] separate subscription service
Browse files Browse the repository at this point in the history
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
  • Loading branch information
MHSanaei and alireza0 committed May 22, 2023
1 parent 1fa9101 commit 769590d
Show file tree
Hide file tree
Showing 18 changed files with 456 additions and 47 deletions.
171 changes: 171 additions & 0 deletions sub/sub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package sub

import (
"context"
"crypto/tls"
"io"
"net"
"net/http"
"strconv"
"strings"
"x-ui/config"
"x-ui/logger"
"x-ui/util/common"
"x-ui/web/network"
"x-ui/web/service"

"github.com/gin-gonic/gin"
)

type Server struct {
httpServer *http.Server
listener net.Listener

sub *SUBController
settingService service.SettingService

ctx context.Context
cancel context.CancelFunc
}

func NewServer() *Server {
ctx, cancel := context.WithCancel(context.Background())
return &Server{
ctx: ctx,
cancel: cancel,
}
}

func (s *Server) initRouter() (*gin.Engine, error) {
if config.IsDebug() {
gin.SetMode(gin.DebugMode)
} else {
gin.DefaultWriter = io.Discard
gin.DefaultErrorWriter = io.Discard
gin.SetMode(gin.ReleaseMode)
}

engine := gin.Default()

subPath, err := s.settingService.GetSubPath()
if err != nil {
return nil, err
}

subDomain, err := s.settingService.GetSubDomain()
if err != nil {
return nil, err
}

if subDomain != "" {
validateDomain := func(c *gin.Context) {
host := strings.Split(c.Request.Host, ":")[0]

if host != subDomain {
c.AbortWithStatus(http.StatusForbidden)
return
}

c.Next()
}

engine.Use(validateDomain)
}

g := engine.Group(subPath)

s.sub = NewSUBController(g)

return engine, nil
}

func (s *Server) Start() (err error) {
//This is an anonymous function, no function name
defer func() {
if err != nil {
s.Stop()
}
}()

subEnable, err := s.settingService.GetSubEnable()
if err != nil {
return err
}
if !subEnable {
return nil
}

engine, err := s.initRouter()
if err != nil {
return err
}

certFile, err := s.settingService.GetSubCertFile()
if err != nil {
return err
}
keyFile, err := s.settingService.GetSubKeyFile()
if err != nil {
return err
}
listen, err := s.settingService.GetSubListen()
if err != nil {
return err
}
port, err := s.settingService.GetSubPort()
if err != nil {
return err
}
listenAddr := net.JoinHostPort(listen, strconv.Itoa(port))
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
return err
}
if certFile != "" || keyFile != "" {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
listener.Close()
return err
}
c := &tls.Config{
Certificates: []tls.Certificate{cert},
}
listener = network.NewAutoHttpsListener(listener)
listener = tls.NewListener(listener, c)
}

if certFile != "" || keyFile != "" {
logger.Info("Sub server run https on", listener.Addr())
} else {
logger.Info("Sub server run http on", listener.Addr())
}
s.listener = listener

s.httpServer = &http.Server{
Handler: engine,
}

go func() {
s.httpServer.Serve(listener)
}()

return nil
}

func (s *Server) Stop() error {
s.cancel()

var err1 error
var err2 error
if s.httpServer != nil {
err1 = s.httpServer.Shutdown(s.ctx)
}
if s.listener != nil {
err2 = s.listener.Close()
}
return common.Combine(err1, err2)
}

func (s *Server) GetCtx() context.Context {
return s.ctx
}
9 changes: 3 additions & 6 deletions web/controller/sub.go → sub/subController.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
package controller
package sub

import (
"encoding/base64"
"strings"
"x-ui/web/service"

"github.com/gin-gonic/gin"
)

type SUBController struct {
BaseController

subService service.SubService
subService SubService
}

func NewSUBController(g *gin.RouterGroup) *SUBController {
Expand All @@ -21,7 +18,7 @@ func NewSUBController(g *gin.RouterGroup) *SUBController {
}

func (a *SUBController) initRouter(g *gin.RouterGroup) {
g = g.Group("/sub")
g = g.Group("/")

g.GET("/:subid", a.subs)
}
Expand Down
75 changes: 67 additions & 8 deletions web/service/sub.go → sub/subService.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package service
package sub

import (
"encoding/base64"
Expand All @@ -8,14 +8,16 @@ import (
"x-ui/database"
"x-ui/database/model"
"x-ui/logger"
"x-ui/web/service"
"x-ui/xray"

"github.com/goccy/go-json"
)

type SubService struct {
address string
inboundService InboundService
inboundService service.InboundService
settingServics service.SettingService
}

func (s *SubService) GetSubs(subId string, host string) ([]string, []string, error) {
Expand All @@ -29,7 +31,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, []string, err
return nil, nil, err
}
for _, inbound := range inbounds {
clients, err := s.inboundService.getClients(inbound)
clients, err := s.inboundService.GetClients(inbound)
if err != nil {
logger.Error("SubService - GetSub: Unable to get clients from inbound")
}
Expand Down Expand Up @@ -66,7 +68,8 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, []string, err
}
}
headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000))
headers = append(headers, "12")
updateInterval, _ := s.settingServics.GetSubUpdates()
headers = append(headers, fmt.Sprintf("%d", updateInterval))
headers = append(headers, subId)
return result, headers, nil
}
Expand Down Expand Up @@ -163,6 +166,7 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
}

security, _ := stream["security"].(string)
var domains []interface{}
obj["tls"] = security
if security == "tls" {
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
Expand All @@ -185,14 +189,17 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
obj["allowInsecure"], _ = insecure.(bool)
}
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
domains, _ = domainSettings.([]interface{})
}
}
serverName, _ := tlsSetting["serverName"].(string)
if serverName != "" {
obj["add"] = serverName
}
}

clients, _ := s.inboundService.getClients(inbound)
clients, _ := s.inboundService.GetClients(inbound)
clientIndex := -1
for i, client := range clients {
if client.Email == email {
Expand All @@ -203,6 +210,21 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
obj["id"] = clients[clientIndex].ID
obj["aid"] = clients[clientIndex].AlterIds

if len(domains) > 0 {
links := ""
for index, d := range domains {
domain := d.(map[string]interface{})
obj["ps"] = remark + "-" + domain["remark"].(string)
obj["add"] = domain["domain"].(string)
if index > 0 {
links += "\n"
}
jsonStr, _ := json.MarshalIndent(obj, "", " ")
links += "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
}
return links
}

jsonStr, _ := json.MarshalIndent(obj, "", " ")
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
}
Expand All @@ -214,7 +236,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
}
var stream map[string]interface{}
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
clients, _ := s.inboundService.getClients(inbound)
clients, _ := s.inboundService.GetClients(inbound)
clientIndex := -1
for i, client := range clients {
if client.Email == email {
Expand Down Expand Up @@ -270,6 +292,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
}

security, _ := stream["security"].(string)
var domains []interface{}
if security == "tls" {
params["security"] = "tls"
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
Expand All @@ -294,6 +317,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
params["allowInsecure"] = "1"
}
}
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
domains, _ = domainSettings.([]interface{})
}
}

if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
Expand Down Expand Up @@ -393,6 +419,20 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
url.RawQuery = q.Encode()

remark := fmt.Sprintf("%s-%s", inbound.Remark, email)

if len(domains) > 0 {
links := ""
for index, d := range domains {
domain := d.(map[string]interface{})
url.Fragment = remark + "-" + domain["remark"].(string)
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
if index > 0 {
links += "\n"
}
links += url.String()
}
return links
}
url.Fragment = remark
return url.String()
}
Expand All @@ -404,7 +444,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
}
var stream map[string]interface{}
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
clients, _ := s.inboundService.getClients(inbound)
clients, _ := s.inboundService.GetClients(inbound)
clientIndex := -1
for i, client := range clients {
if client.Email == email {
Expand Down Expand Up @@ -460,6 +500,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
}

security, _ := stream["security"].(string)
var domains []interface{}
if security == "tls" {
params["security"] = "tls"
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
Expand All @@ -484,6 +525,9 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
params["allowInsecure"] = "1"
}
}
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
domains, _ = domainSettings.([]interface{})
}
}

serverName, _ := tlsSetting["serverName"].(string)
Expand Down Expand Up @@ -580,6 +624,21 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
url.RawQuery = q.Encode()

remark := fmt.Sprintf("%s-%s", inbound.Remark, email)

if len(domains) > 0 {
links := ""
for index, d := range domains {
domain := d.(map[string]interface{})
url.Fragment = remark + "-" + domain["remark"].(string)
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
if index > 0 {
links += "\n"
}
links += url.String()
}
return links
}

url.Fragment = remark
return url.String()
}
Expand All @@ -589,7 +648,7 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
if inbound.Protocol != model.Shadowsocks {
return ""
}
clients, _ := s.inboundService.getClients(inbound)
clients, _ := s.inboundService.GetClients(inbound)

var settings map[string]interface{}
json.Unmarshal([]byte(inbound.Settings), &settings)
Expand Down
Loading

0 comments on commit 769590d

Please sign in to comment.