mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-04-19 13:32:24 +00:00
full multiuser shadowsocks
full multiuser shadowsocks + fix logs after api changes Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
This commit is contained in:
parent
4cfed17650
commit
145ea1e6f1
8 changed files with 87 additions and 27 deletions
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 = [];
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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" {
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
Loading…
Reference in a new issue