| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385 |
- 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
- }
|