Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'sub/subClashService.go')
-rw-r--r--sub/subClashService.go385
1 files changed, 385 insertions, 0 deletions
diff --git a/sub/subClashService.go b/sub/subClashService.go
new file mode 100644
index 00000000..ea095919
--- /dev/null
+++ b/sub/subClashService.go
@@ -0,0 +1,385 @@
+package sub
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/goccy/go-json"
+ yaml "github.com/goccy/go-yaml"
+
+ "github.com/mhsanaei/3x-ui/v2/database/model"
+ "github.com/mhsanaei/3x-ui/v2/logger"
+ "github.com/mhsanaei/3x-ui/v2/web/service"
+ "github.com/mhsanaei/3x-ui/v2/xray"
+)
+
+type SubClashService struct {
+ inboundService service.InboundService
+ SubService *SubService
+}
+
+type ClashConfig struct {
+ Proxies []map[string]any `yaml:"proxies"`
+ ProxyGroups []map[string]any `yaml:"proxy-groups"`
+ Rules []string `yaml:"rules"`
+}
+
+func NewSubClashService(subService *SubService) *SubClashService {
+ return &SubClashService{SubService: subService}
+}
+
+func (s *SubClashService) GetClash(subId string, host string) (string, string, error) {
+ inbounds, err := s.SubService.getInboundsBySubId(subId)
+ if err != nil || len(inbounds) == 0 {
+ return "", "", err
+ }
+
+ var traffic xray.ClientTraffic
+ var clientTraffics []xray.ClientTraffic
+ var proxies []map[string]any
+
+ for _, inbound := range inbounds {
+ clients, err := s.inboundService.GetClients(inbound)
+ if err != nil {
+ logger.Error("SubClashService - GetClients: Unable to get clients from inbound")
+ }
+ if clients == nil {
+ continue
+ }
+ if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
+ listen, port, streamSettings, err := s.SubService.getFallbackMaster(inbound.Listen, inbound.StreamSettings)
+ if err == nil {
+ inbound.Listen = listen
+ inbound.Port = port
+ inbound.StreamSettings = streamSettings
+ }
+ }
+ for _, client := range clients {
+ if client.Enable && client.SubID == subId {
+ clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email))
+ proxies = append(proxies, s.getProxies(inbound, client, host)...)
+ }
+ }
+ }
+
+ if len(proxies) == 0 {
+ return "", "", nil
+ }
+
+ for index, clientTraffic := range clientTraffics {
+ if index == 0 {
+ traffic.Up = clientTraffic.Up
+ traffic.Down = clientTraffic.Down
+ traffic.Total = clientTraffic.Total
+ if clientTraffic.ExpiryTime > 0 {
+ traffic.ExpiryTime = clientTraffic.ExpiryTime
+ }
+ } else {
+ traffic.Up += clientTraffic.Up
+ traffic.Down += clientTraffic.Down
+ if traffic.Total == 0 || clientTraffic.Total == 0 {
+ traffic.Total = 0
+ } else {
+ traffic.Total += clientTraffic.Total
+ }
+ if clientTraffic.ExpiryTime != traffic.ExpiryTime {
+ traffic.ExpiryTime = 0
+ }
+ }
+ }
+
+ proxyNames := make([]string, 0, len(proxies)+1)
+ for _, proxy := range proxies {
+ if name, ok := proxy["name"].(string); ok && name != "" {
+ proxyNames = append(proxyNames, name)
+ }
+ }
+ proxyNames = append(proxyNames, "DIRECT")
+
+ config := ClashConfig{
+ Proxies: proxies,
+ ProxyGroups: []map[string]any{{
+ "name": "PROXY",
+ "type": "select",
+ "proxies": proxyNames,
+ }},
+ Rules: []string{"MATCH,PROXY"},
+ }
+
+ finalYAML, err := yaml.Marshal(config)
+ if err != nil {
+ return "", "", err
+ }
+
+ header := fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
+ return string(finalYAML), header, nil
+}
+
+func (s *SubClashService) getProxies(inbound *model.Inbound, client model.Client, host string) []map[string]any {
+ stream := s.streamData(inbound.StreamSettings)
+ externalProxies, ok := stream["externalProxy"].([]any)
+ if !ok || len(externalProxies) == 0 {
+ externalProxies = []any{map[string]any{
+ "forceTls": "same",
+ "dest": host,
+ "port": float64(inbound.Port),
+ "remark": "",
+ }}
+ }
+ delete(stream, "externalProxy")
+
+ proxies := make([]map[string]any, 0, len(externalProxies))
+ for _, ep := range externalProxies {
+ extPrxy := ep.(map[string]any)
+ workingInbound := *inbound
+ workingInbound.Listen = extPrxy["dest"].(string)
+ workingInbound.Port = int(extPrxy["port"].(float64))
+ workingStream := cloneMap(stream)
+
+ switch extPrxy["forceTls"].(string) {
+ case "tls":
+ if workingStream["security"] != "tls" {
+ workingStream["security"] = "tls"
+ workingStream["tlsSettings"] = map[string]any{}
+ }
+ case "none":
+ if workingStream["security"] != "none" {
+ workingStream["security"] = "none"
+ delete(workingStream, "tlsSettings")
+ delete(workingStream, "realitySettings")
+ }
+ }
+
+ proxy := s.buildProxy(&workingInbound, client, workingStream, extPrxy["remark"].(string))
+ if len(proxy) > 0 {
+ proxies = append(proxies, proxy)
+ }
+ }
+ return proxies
+}
+
+func (s *SubClashService) buildProxy(inbound *model.Inbound, client model.Client, stream map[string]any, extraRemark string) map[string]any {
+ proxy := map[string]any{
+ "name": s.SubService.genRemark(inbound, client.Email, extraRemark),
+ "server": inbound.Listen,
+ "port": inbound.Port,
+ "udp": true,
+ }
+
+ network, _ := stream["network"].(string)
+ if !s.applyTransport(proxy, network, stream) {
+ return nil
+ }
+
+ switch inbound.Protocol {
+ case model.VMESS:
+ proxy["type"] = "vmess"
+ proxy["uuid"] = client.ID
+ proxy["alterId"] = 0
+ cipher := client.Security
+ if cipher == "" {
+ cipher = "auto"
+ }
+ proxy["cipher"] = cipher
+ case model.VLESS:
+ proxy["type"] = "vless"
+ proxy["uuid"] = client.ID
+ if client.Flow != "" && network == "tcp" {
+ proxy["flow"] = client.Flow
+ }
+ var inboundSettings map[string]any
+ json.Unmarshal([]byte(inbound.Settings), &inboundSettings)
+ if encryption, ok := inboundSettings["encryption"].(string); ok && encryption != "" {
+ proxy["packet-encoding"] = encryption
+ }
+ case model.Trojan:
+ proxy["type"] = "trojan"
+ proxy["password"] = client.Password
+ case model.Shadowsocks:
+ proxy["type"] = "ss"
+ proxy["password"] = client.Password
+ var inboundSettings map[string]any
+ json.Unmarshal([]byte(inbound.Settings), &inboundSettings)
+ method, _ := inboundSettings["method"].(string)
+ if method == "" {
+ return nil
+ }
+ proxy["cipher"] = method
+ if strings.HasPrefix(method, "2022") {
+ if serverPassword, ok := inboundSettings["password"].(string); ok && serverPassword != "" {
+ proxy["password"] = fmt.Sprintf("%s:%s", serverPassword, client.Password)
+ }
+ }
+ default:
+ return nil
+ }
+
+ security, _ := stream["security"].(string)
+ if !s.applySecurity(proxy, security, stream) {
+ return nil
+ }
+
+ return proxy
+}
+
+func (s *SubClashService) applyTransport(proxy map[string]any, network string, stream map[string]any) bool {
+ switch network {
+ case "", "tcp":
+ proxy["network"] = "tcp"
+ tcp, _ := stream["tcpSettings"].(map[string]any)
+ if tcp != nil {
+ header, _ := tcp["header"].(map[string]any)
+ if header != nil {
+ typeStr, _ := header["type"].(string)
+ if typeStr != "" && typeStr != "none" {
+ return false
+ }
+ }
+ }
+ return true
+ case "ws":
+ proxy["network"] = "ws"
+ ws, _ := stream["wsSettings"].(map[string]any)
+ wsOpts := map[string]any{}
+ if ws != nil {
+ if path, ok := ws["path"].(string); ok && path != "" {
+ wsOpts["path"] = path
+ }
+ host := ""
+ if v, ok := ws["host"].(string); ok && v != "" {
+ host = v
+ } else if headers, ok := ws["headers"].(map[string]any); ok {
+ host = searchHost(headers)
+ }
+ if host != "" {
+ wsOpts["headers"] = map[string]any{"Host": host}
+ }
+ }
+ if len(wsOpts) > 0 {
+ proxy["ws-opts"] = wsOpts
+ }
+ return true
+ case "grpc":
+ proxy["network"] = "grpc"
+ grpc, _ := stream["grpcSettings"].(map[string]any)
+ grpcOpts := map[string]any{}
+ if grpc != nil {
+ if serviceName, ok := grpc["serviceName"].(string); ok && serviceName != "" {
+ grpcOpts["grpc-service-name"] = serviceName
+ }
+ }
+ if len(grpcOpts) > 0 {
+ proxy["grpc-opts"] = grpcOpts
+ }
+ return true
+ default:
+ return false
+ }
+}
+
+func (s *SubClashService) applySecurity(proxy map[string]any, security string, stream map[string]any) bool {
+ switch security {
+ case "", "none":
+ proxy["tls"] = false
+ return true
+ case "tls":
+ proxy["tls"] = true
+ tlsSettings, _ := stream["tlsSettings"].(map[string]any)
+ if tlsSettings != nil {
+ if serverName, ok := tlsSettings["serverName"].(string); ok && serverName != "" {
+ proxy["servername"] = serverName
+ switch proxy["type"] {
+ case "trojan":
+ proxy["sni"] = serverName
+ }
+ }
+ if fingerprint, ok := tlsSettings["fingerprint"].(string); ok && fingerprint != "" {
+ proxy["client-fingerprint"] = fingerprint
+ }
+ }
+ return true
+ case "reality":
+ proxy["tls"] = true
+ realitySettings, _ := stream["realitySettings"].(map[string]any)
+ if realitySettings == nil {
+ return false
+ }
+ if serverName, ok := realitySettings["serverName"].(string); ok && serverName != "" {
+ proxy["servername"] = serverName
+ }
+ realityOpts := map[string]any{}
+ if publicKey, ok := realitySettings["publicKey"].(string); ok && publicKey != "" {
+ realityOpts["public-key"] = publicKey
+ }
+ if shortID, ok := realitySettings["shortId"].(string); ok && shortID != "" {
+ realityOpts["short-id"] = shortID
+ }
+ if len(realityOpts) > 0 {
+ proxy["reality-opts"] = realityOpts
+ }
+ if fingerprint, ok := realitySettings["fingerprint"].(string); ok && fingerprint != "" {
+ proxy["client-fingerprint"] = fingerprint
+ }
+ return true
+ default:
+ return false
+ }
+}
+
+func (s *SubClashService) streamData(stream string) map[string]any {
+ var streamSettings map[string]any
+ json.Unmarshal([]byte(stream), &streamSettings)
+ security, _ := streamSettings["security"].(string)
+ switch security {
+ case "tls":
+ if tlsSettings, ok := streamSettings["tlsSettings"].(map[string]any); ok {
+ streamSettings["tlsSettings"] = s.tlsData(tlsSettings)
+ }
+ case "reality":
+ if realitySettings, ok := streamSettings["realitySettings"].(map[string]any); ok {
+ streamSettings["realitySettings"] = s.realityData(realitySettings)
+ }
+ }
+ delete(streamSettings, "sockopt")
+ return streamSettings
+}
+
+func (s *SubClashService) tlsData(tData map[string]any) map[string]any {
+ tlsData := make(map[string]any, 1)
+ tlsClientSettings, _ := tData["settings"].(map[string]any)
+ tlsData["serverName"] = tData["serverName"]
+ tlsData["alpn"] = tData["alpn"]
+ if fingerprint, ok := tlsClientSettings["fingerprint"].(string); ok {
+ tlsData["fingerprint"] = fingerprint
+ }
+ return tlsData
+}
+
+func (s *SubClashService) realityData(rData map[string]any) map[string]any {
+ rDataOut := make(map[string]any, 1)
+ realityClientSettings, _ := rData["settings"].(map[string]any)
+ if publicKey, ok := realityClientSettings["publicKey"].(string); ok {
+ rDataOut["publicKey"] = publicKey
+ }
+ if fingerprint, ok := realityClientSettings["fingerprint"].(string); ok {
+ rDataOut["fingerprint"] = fingerprint
+ }
+ if serverNames, ok := rData["serverNames"].([]any); ok && len(serverNames) > 0 {
+ rDataOut["serverName"] = fmt.Sprint(serverNames[0])
+ }
+ if shortIDs, ok := rData["shortIds"].([]any); ok && len(shortIDs) > 0 {
+ rDataOut["shortId"] = fmt.Sprint(shortIDs[0])
+ }
+ return rDataOut
+}
+
+func cloneMap(src map[string]any) map[string]any {
+ if src == nil {
+ return nil
+ }
+ dst := make(map[string]any, len(src))
+ for k, v := range src {
+ dst[k] = v
+ }
+ return dst
+}