diff options
| author | MHSanaei <ho3ein.sanaei@gmail.com> | 2024-02-21 13:47:52 +0300 |
|---|---|---|
| committer | MHSanaei <ho3ein.sanaei@gmail.com> | 2024-02-21 13:47:52 +0300 |
| commit | 03b7a3479394f54a2e793f23f35e0f2b8a4b4a6a (patch) | |
| tree | 72e4bd1e7201f90bc118b8ee0f2bc86d4718bb97 /sub/subJsonService.go | |
| parent | f3eb4f055db80372298d60c073870a5af1431785 (diff) | |
[sub] json + fragment
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
Diffstat (limited to 'sub/subJsonService.go')
| -rw-r--r-- | sub/subJsonService.go | 355 |
1 files changed, 355 insertions, 0 deletions
diff --git a/sub/subJsonService.go b/sub/subJsonService.go new file mode 100644 index 00000000..92519f3e --- /dev/null +++ b/sub/subJsonService.go @@ -0,0 +1,355 @@ +package sub + +import ( + _ "embed" + "encoding/json" + "fmt" + "strings" + "x-ui/database/model" + "x-ui/logger" + "x-ui/util/json_util" + "x-ui/util/random" + "x-ui/web/service" + "x-ui/xray" +) + +//go:embed default.json +var defaultJson string + +type SubJsonService struct { + fragmanet string + + inboundService service.InboundService + SubService +} + +func NewSubJsonService(fragment string) *SubJsonService { + return &SubJsonService{ + fragmanet: fragment, + } +} + +func (s *SubJsonService) GetJson(subId string, host string) (string, string, error) { + inbounds, err := s.SubService.getInboundsBySubId(subId) + if err != nil || len(inbounds) == 0 { + return "", "", err + } + + var header string + var traffic xray.ClientTraffic + var clientTraffics []xray.ClientTraffic + var configJson map[string]interface{} + var defaultOutbounds []json_util.RawMessage + + json.Unmarshal([]byte(defaultJson), &configJson) + if outboundSlices, ok := configJson["outbounds"].([]interface{}); ok { + for _, defaultOutbound := range outboundSlices { + jsonBytes, _ := json.Marshal(defaultOutbound) + defaultOutbounds = append(defaultOutbounds, jsonBytes) + } + } + + outbounds := []json_util.RawMessage{} + startIndex := 0 + // Prepare Inbounds + for _, inbound := range inbounds { + clients, err := s.inboundService.GetClients(inbound) + if err != nil { + logger.Error("SubJsonService - GetClients: Unable to get clients from inbound") + } + if clients == nil { + continue + } + if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' { + listen, port, streamSettings, err := s.getFallbackMaster(inbound.Listen, inbound.StreamSettings) + if err == nil { + inbound.Listen = listen + inbound.Port = port + inbound.StreamSettings = streamSettings + } + } + + var subClients []model.Client + for _, client := range clients { + if client.Enable && client.SubID == subId { + subClients = append(subClients, client) + clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email)) + } + } + + outbound := s.getOutbound(inbound, subClients, host, startIndex) + if outbound != nil { + outbounds = append(outbounds, outbound...) + startIndex += len(outbound) + } + } + + if len(outbounds) == 0 { + return "", "", nil + } + + // Prepare statistics + 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 + } + } + } + + if s.fragmanet != "" { + outbounds = append(outbounds, json_util.RawMessage(s.fragmanet)) + } + + // Combile outbounds + outbounds = append(outbounds, defaultOutbounds...) + configJson["outbounds"] = outbounds + finalJson, _ := json.MarshalIndent(configJson, "", " ") + + header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000) + return string(finalJson), header, nil +} + +func (s *SubJsonService) getOutbound(inbound *model.Inbound, clients []model.Client, host string, startIndex int) []json_util.RawMessage { + var newOutbounds []json_util.RawMessage + stream := s.streamData(inbound.StreamSettings) + + externalProxies, ok := stream["externalProxy"].([]interface{}) + if !ok || len(externalProxies) == 0 { + externalProxies = []interface{}{ + map[string]interface{}{ + "forceTls": "same", + "dest": host, + "port": float64(inbound.Port), + }, + } + } + + delete(stream, "externalProxy") + + config_index := startIndex + for _, ep := range externalProxies { + extPrxy := ep.(map[string]interface{}) + inbound.Listen = extPrxy["dest"].(string) + inbound.Port = int(extPrxy["port"].(float64)) + newStream := stream + switch extPrxy["forceTls"].(string) { + case "tls": + if newStream["security"] != "tls" { + newStream["security"] = "tls" + newStream["tslSettings"] = map[string]interface{}{} + } + case "none": + if newStream["security"] != "none" { + newStream["security"] = "none" + delete(newStream, "tslSettings") + } + } + streamSettings, _ := json.MarshalIndent(newStream, "", " ") + inbound.StreamSettings = string(streamSettings) + + for _, client := range clients { + inbound.Tag = fmt.Sprintf("proxy_%d", config_index) + switch inbound.Protocol { + case "vmess", "vless": + newOutbounds = append(newOutbounds, s.genVnext(inbound, client)) + case "trojan", "shadowsocks": + newOutbounds = append(newOutbounds, s.genServer(inbound, client)) + } + config_index += 1 + } + } + + return newOutbounds +} + +func (s *SubJsonService) streamData(stream string) map[string]interface{} { + var streamSettings map[string]interface{} + json.Unmarshal([]byte(stream), &streamSettings) + security, _ := streamSettings["security"].(string) + if security == "tls" { + streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]interface{})) + } else if security == "reality" { + streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]interface{})) + } + delete(streamSettings, "sockopt") + + if s.fragmanet != "" { + streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "TcpNoDelay": true}`) + } + + // remove proxy protocol + network, _ := streamSettings["network"].(string) + switch network { + case "tcp": + streamSettings["tcpSettings"] = s.removeAcceptProxy(streamSettings["tcpSettings"]) + case "ws": + streamSettings["wsSettings"] = s.removeAcceptProxy(streamSettings["wsSettings"]) + } + + return streamSettings +} + +func (s *SubJsonService) removeAcceptProxy(setting interface{}) map[string]interface{} { + netSettings, ok := setting.(map[string]interface{}) + if ok { + delete(netSettings, "acceptProxyProtocol") + } + return netSettings +} + +func (s *SubJsonService) tlsData(tData map[string]interface{}) map[string]interface{} { + tlsData := make(map[string]interface{}, 1) + tlsClientSettings := tData["settings"].(map[string]interface{}) + + tlsData["serverName"] = tData["serverName"] + tlsData["alpn"] = tData["alpn"] + if allowInsecure, ok := tlsClientSettings["allowInsecure"].(string); ok { + tlsData["allowInsecure"] = allowInsecure + } + if fingerprint, ok := tlsClientSettings["fingerprint"].(string); ok { + tlsData["fingerprint"] = fingerprint + } + return tlsData +} + +func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]interface{} { + rltyData := make(map[string]interface{}, 1) + rltyClientSettings := rData["settings"].(map[string]interface{}) + + rltyData["show"] = false + rltyData["publicKey"] = rltyClientSettings["publicKey"] + rltyData["fingerprint"] = rltyClientSettings["fingerprint"] + + // Set random data + rltyData["spiderX"] = "/" + random.Seq(15) + shortIds, ok := rData["shortIds"].([]interface{}) + if ok && len(shortIds) > 0 { + rltyData["shortId"] = shortIds[random.Num(len(shortIds))].(string) + } else { + rltyData["shortId"] = "" + } + serverNames, ok := rData["serverNames"].([]interface{}) + if ok && len(serverNames) > 0 { + rltyData["serverName"] = serverNames[random.Num(len(serverNames))].(string) + } else { + rltyData["serverName"] = "" + } + + return rltyData +} + +func (s *SubJsonService) genVnext(inbound *model.Inbound, client model.Client) json_util.RawMessage { + outbound := Outbound{} + usersData := make([]UserVnext, 1) + + usersData[0].ID = client.ID + usersData[0].Level = 8 + if inbound.Protocol == model.VLESS { + usersData[0].Flow = client.Flow + usersData[0].Encryption = "none" + } + + vnextData := make([]VnextSetting, 1) + vnextData[0] = VnextSetting{ + Address: inbound.Listen, + Port: inbound.Port, + Users: usersData, + } + + outbound.Protocol = string(inbound.Protocol) + outbound.Tag = inbound.Tag + outbound.StreamSettings = json_util.RawMessage(inbound.StreamSettings) + outbound.Settings = OutboundSettings{ + Vnext: vnextData, + } + + result, _ := json.MarshalIndent(outbound, "", " ") + return result +} + +func (s *SubJsonService) genServer(inbound *model.Inbound, client model.Client) json_util.RawMessage { + outbound := Outbound{} + + serverData := make([]ServerSetting, 1) + serverData[0] = ServerSetting{ + Address: inbound.Listen, + Port: inbound.Port, + Level: 8, + Password: client.Password, + } + + if inbound.Protocol == model.Shadowsocks { + var inboundSettings map[string]interface{} + json.Unmarshal([]byte(inbound.Settings), &inboundSettings) + method, _ := inboundSettings["method"].(string) + serverData[0].Method = method + + // server password in multi-user 2022 protocols + if strings.HasPrefix(method, "2022") { + if serverPassword, ok := inboundSettings["password"].(string); ok { + serverData[0].Password = fmt.Sprintf("%s:%s", serverPassword, client.Password) + } + } + } + + outbound.Protocol = string(inbound.Protocol) + outbound.Tag = inbound.Tag + outbound.StreamSettings = json_util.RawMessage(inbound.StreamSettings) + outbound.Settings = OutboundSettings{ + Servers: serverData, + } + + result, _ := json.MarshalIndent(outbound, "", " ") + return result +} + +type Outbound struct { + Protocol string `json:"protocol"` + Tag string `json:"tag"` + StreamSettings json_util.RawMessage `json:"streamSettings"` + Mux map[string]interface{} `json:"mux,omitempty"` + ProxySettings map[string]interface{} `json:"proxySettings,omitempty"` + Settings OutboundSettings `json:"settings,omitempty"` +} + +type OutboundSettings struct { + Vnext []VnextSetting `json:"vnext,omitempty"` + Servers []ServerSetting `json:"servers,omitempty"` +} + +type VnextSetting struct { + Address string `json:"address"` + Port int `json:"port"` + Users []UserVnext `json:"users"` +} + +type UserVnext struct { + Encryption string `json:"encryption,omitempty"` + Flow string `json:"flow,omitempty"` + ID string `json:"id"` + Level int `json:"level"` +} + +type ServerSetting struct { + Password string `json:"password"` + Level int `json:"level"` + Address string `json:"address"` + Port int `json:"port"` + Flow string `json:"flow,omitempty"` + Method string `json:"method,omitempty"` +} |
