Преглед изворни кода

full multiuser shadowsocks

full multiuser shadowsocks +
fix logs after api changes

Co-Authored-By: Alireza Ahmadi <[email protected]>
MHSanaei пре 1 година
родитељ
комит
145ea1e6f1

+ 4 - 1
sub/subService.go

@@ -755,7 +755,10 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string, ex
 		}
 	}
 
-	encPart := fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
+	encPart := fmt.Sprintf("%s:%s", method, clients[clientIndex].Password)
+	if method[0] == '2' {
+		encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
+	}
 	link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port)
 	url, _ := url.Parse(link)
 	q := url.Query()

+ 14 - 5
web/assets/js/model/xray.js

@@ -16,9 +16,10 @@ const VmessMethods = {
 };
 
 const SSMethods = {
-    CHACHA20_POLY1305: 'chacha20-poly1305',
     AES_256_GCM: 'aes-256-gcm',
     AES_128_GCM: 'aes-128-gcm',
+    CHACHA20_POLY1305: 'chacha20-poly1305',
+    XCHACHA20_POLY1305: 'xchacha20-poly1305',
     BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
     BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
     BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
@@ -1040,7 +1041,10 @@ class Inbound extends XrayCommonClass {
         }
     }
     get isSSMultiUser() {
-        return [SSMethods.BLAKE3_AES_128_GCM,SSMethods.BLAKE3_AES_256_GCM].includes(this.method);
+        return this.method != SSMethods.BLAKE3_CHACHA20_POLY1305;
+    }
+    get isSS2022(){
+        return this.method.substring(0,4) === "2022";
     }
 
     get serverName() {
@@ -1470,9 +1474,11 @@ class Inbound extends XrayCommonClass {
                 break;
         }
 
-        let clientPassword = this.isSSMultiUser ? ':' + settings.shadowsockses[clientIndex].password : '';
+        let password = new Array();
+        if (this.isSSMultiUser) password.push(settings.shadowsockses[clientIndex].password);
+        if (this.isSS2022) password.push(settings.password);
 
-        let link = `ss://${safeBase64(settings.method + ':' + settings.password + clientPassword)}@${address}:${this.port}`;
+        let link = `ss://${safeBase64(settings.method + ':' + password.join(':'))}@${address}:${this.port}`;
         const url = new URL(link);
         for (const [key, value] of params) {
             url.searchParams.set(key, value)
@@ -2097,8 +2103,9 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
 };
 
 Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
-    constructor(password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
+    constructor(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
         super();
+        this.method = method;
         this.password = password;
         this.email = email;
         this.limitIp = limitIp;
@@ -2111,6 +2118,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
 
     toJson() {
         return {
+            method: this.method,
             password: this.password,
             email: this.email,
             limitIp: this.limitIp,
@@ -2124,6 +2132,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
 
     static fromJson(json = {}) {
         return new Inbound.ShadowsocksSettings.Shadowsocks(
+            json.method,
             json.password,
             json.email,
             json.limitIp,

+ 1 - 1
web/html/xui/client_modal.html

@@ -70,7 +70,7 @@
                 case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess());
                 case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
                 case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan());
-                case Protocols.SHADOWSOCKS: return clients.push(new Inbound.ShadowsocksSettings.Shadowsocks());
+                case Protocols.SHADOWSOCKS: return clients.push(new Inbound.ShadowsocksSettings.Shadowsocks(clients[0].method));
                 default: return null;
             }
         },

+ 1 - 1
web/html/xui/form/protocol/shadowsocks.html

@@ -115,7 +115,7 @@
             <a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
         </a-select>
     </a-form-item>
-    <a-form-item label='{{ i18n "password" }}'>
+    <a-form-item v-if="inbound.isSS2022" label='{{ i18n "password" }}'>
         <a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
         <a-input v-model.trim="inbound.settings.password" style="width: 250px;"></a-input>
     </a-form-item>

+ 9 - 0
web/html/xui/inbound_modal.html

@@ -110,6 +110,15 @@
                     if (this.inModal.inbound.settings.shadowsockses.length ==0){
                         this.inModal.inbound.settings.shadowsockses = [new Inbound.ShadowsocksSettings.Shadowsocks()];
                     }
+                    if (["aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305"].includes(this.inModal.inbound.settings.method)) {
+                        this.inModal.inbound.settings.shadowsockses.forEach(client => {
+                            client.method = this.inModal.inbound.settings.method;
+                        })
+                    } else {
+                        this.inModal.inbound.settings.shadowsockses.forEach(client => {
+                            client.method = "";
+                        })
+                    }
                 } else {
                     if (this.inModal.inbound.settings.shadowsockses.length > 0){
                         this.inModal.inbound.settings.shadowsockses = [];

+ 55 - 18
web/service/inbound.go

@@ -327,14 +327,15 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
 			inboundJson, err2 := json.MarshalIndent(oldInbound.GenXrayInboundConfig(), "", "  ")
 			if err2 != nil {
 				logger.Debug("Unable to marshal updated inbound config:", err2)
-			}
-
-			err2 = s.xrayApi.AddInbound(inboundJson)
-			if err1 == nil {
-				logger.Debug("Updated inbound added by api:", oldInbound.Tag)
-			} else {
-				logger.Debug("Unable to update inbound by api:", err2)
 				needRestart = true
+			} else {
+				err2 = s.xrayApi.AddInbound(inboundJson)
+				if err2 == nil {
+					logger.Debug("Updated inbound added by api:", oldInbound.Tag)
+				} else {
+					logger.Debug("Unable to update inbound by api:", err2)
+					needRestart = true
+				}
 			}
 		}
 	}
@@ -461,15 +462,21 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
 		if len(client.Email) > 0 {
 			s.AddClientStat(tx, data.Id, &client)
 			if client.Enable {
+				cipher := ""
+				if oldInbound.Protocol == "shadowsocks" {
+					cipher = oldSettings["method"].(string)
+				}
 				err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]interface{}{
 					"email":    client.Email,
 					"id":       client.ID,
 					"flow":     client.Flow,
 					"password": client.Password,
+					"cipher":   cipher,
 				})
 				if err1 == nil {
 					logger.Debug("Client added by api:", client.Email)
 				} else {
+					logger.Debug("Error in adding client by api:", err1)
 					needRestart = true
 				}
 			}
@@ -536,15 +543,18 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
 		return false, err
 	}
 	needRestart := false
-	s.xrayApi.Init(p.GetAPIPort())
 	if len(email) > 0 {
-		err = s.xrayApi.RemoveUser(oldInbound.Tag, email)
-		if err == nil {
+		s.xrayApi.Init(p.GetAPIPort())
+		err1 := s.xrayApi.RemoveUser(oldInbound.Tag, email)
+		if err1 == nil {
 			logger.Debug("Client deleted by api:", email)
 			needRestart = false
+		} else {
+			logger.Debug("Unable to del client by api:", err1)
+			needRestart = true
 		}
+		s.xrayApi.Close()
 	}
-	s.xrayApi.Close()
 	return needRestart, db.Save(oldInbound).Error
 }
 
@@ -650,26 +660,35 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
 		}
 	}
 	needRestart := false
-	s.xrayApi.Init(p.GetAPIPort())
 	if len(oldEmail) > 0 {
+		s.xrayApi.Init(p.GetAPIPort())
 		s.xrayApi.RemoveUser(oldInbound.Tag, oldEmail)
 		if clients[0].Enable {
+			cipher := ""
+			if oldInbound.Protocol == "shadowsocks" {
+				cipher = oldSettings["method"].(string)
+			}
 			err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]interface{}{
 				"email":    clients[0].Email,
 				"id":       clients[0].ID,
 				"flow":     clients[0].Flow,
 				"password": clients[0].Password,
+				"cipher":   cipher,
 			})
 			if err1 == nil {
 				logger.Debug("Client edited by api:", clients[0].Email)
-				needRestart = false
+			} else {
+				logger.Debug("Error in adding client by api:", err1)
+				needRestart = true
 			}
 		} else {
 			logger.Debug("Client disabled by api:", clients[0].Email)
-			needRestart = false
 		}
+		s.xrayApi.Close()
+	} else {
+		logger.Debug("Client old email not found")
+		needRestart = true
 	}
-	s.xrayApi.Close()
 	return needRestart, tx.Save(oldInbound).Error
 }
 
@@ -723,6 +742,11 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
 		return err
 	}
 
+	// Avoid empty slice error
+	if len(dbClientTraffics) == 0 {
+		return nil
+	}
+
 	dbClientTraffics, err = s.adjustTraffics(tx, dbClientTraffics)
 	if err != nil {
 		return err
@@ -814,10 +838,11 @@ func (s *InboundService) DisableInvalidInbounds() (bool, int64, error) {
 		}
 		s.xrayApi.Init(p.GetAPIPort())
 		for _, tag := range tags {
-			err = s.xrayApi.DelInbound(tag)
+			err1 := s.xrayApi.DelInbound(tag)
 			if err == nil {
 				logger.Debug("Inbound disabled by api:", tag)
 			} else {
+				logger.Debug("Error in disabling inbound by api:", err1)
 				needRestart = true
 			}
 		}
@@ -853,10 +878,11 @@ func (s *InboundService) DisableInvalidClients() (bool, int64, error) {
 		}
 		s.xrayApi.Init(p.GetAPIPort())
 		for _, result := range results {
-			err = s.xrayApi.RemoveUser(result.Tag, result.Email)
-			if err == nil {
+			err1 := s.xrayApi.RemoveUser(result.Tag, result.Email)
+			if err1 == nil {
 				logger.Debug("Client disabled by api:", result.Email)
 			} else {
+				logger.Debug("Error in disabling client by api:", err1)
 				needRestart = true
 			}
 		}
@@ -1254,15 +1280,26 @@ func (s *InboundService) ResetClientTraffic(id int, clientEmail string) (bool, e
 		for _, client := range clients {
 			if client.Email == clientEmail {
 				s.xrayApi.Init(p.GetAPIPort())
+				cipher := ""
+				if string(inbound.Protocol) == "shadowsocks" {
+					var oldSettings map[string]interface{}
+					err = json.Unmarshal([]byte(inbound.Settings), &oldSettings)
+					if err != nil {
+						return false, err
+					}
+					cipher = oldSettings["method"].(string)
+				}
 				err1 := s.xrayApi.AddUser(string(inbound.Protocol), inbound.Tag, map[string]interface{}{
 					"email":    client.Email,
 					"id":       client.ID,
 					"flow":     client.Flow,
 					"password": client.Password,
+					"cipher":   cipher,
 				})
 				if err1 == nil {
 					logger.Debug("Client enabled due to reset traffic:", clientEmail)
 				} else {
+					logger.Debug("Error in enabling client by api:", err1)
 					needRestart = true
 				}
 				s.xrayApi.Close()

+ 1 - 1
web/service/xray.go

@@ -116,7 +116,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
 					}
 				}
 				for key := range c {
-					if key != "email" && key != "id" && key != "password" && key != "flow" {
+					if key != "email" && key != "id" && key != "password" && key != "flow" && key != "method" {
 						delete(c, key)
 					}
 					if c["flow"] == "xtls-rprx-vision-udp443" {

+ 2 - 0
xray/api.go

@@ -63,10 +63,12 @@ func (x *XrayAPI) AddInbound(inbound []byte) error {
 	err := json.Unmarshal(inbound, conf)
 	if err != nil {
 		logger.Debug("Failed to unmarshal inbound:", err)
+		return err
 	}
 	config, err := conf.Build()
 	if err != nil {
 		logger.Debug("Failed to build inbound Detur:", err)
+		return err
 	}
 	inboundConfig := command.AddInboundRequest{Inbound: config}