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
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/assets/js/model/models.js12
-rw-r--r--web/assets/js/model/xray.js38
-rw-r--r--web/controller/inbound.go21
-rw-r--r--web/html/login.html2
-rw-r--r--web/html/xui/form/protocol/trojan.html35
-rw-r--r--web/html/xui/form/protocol/vless.html36
-rw-r--r--web/html/xui/form/protocol/vmess.html33
-rw-r--r--web/html/xui/inbound_modal.html24
-rw-r--r--web/html/xui/inbounds.html13
-rw-r--r--web/job/check_clinet_ip_job.go351
-rw-r--r--web/service/inbound.go22
-rw-r--r--web/service/server.go4
-rw-r--r--web/translation/translate.fa_IR.toml2
-rw-r--r--web/web.go3
14 files changed, 584 insertions, 12 deletions
diff --git a/web/assets/js/model/models.js b/web/assets/js/model/models.js
index 9fa5e7fc..80132a1e 100644
--- a/web/assets/js/model/models.js
+++ b/web/assets/js/model/models.js
@@ -36,7 +36,7 @@ class DBInbound {
this.remark = "";
this.enable = true;
this.expiryTime = 0;
-
+ this.iplimit = 0;
this.listen = "";
this.port = 0;
this.protocol = "";
@@ -109,6 +109,10 @@ class DBInbound {
get isExpiry() {
return this.expiryTime < new Date().getTime();
}
+ get isDBInboundEmpty() {
+ const inbound = this.toInbound();
+ return inbound.isInboundEmpty();
+ }
toInbound() {
let settings = {};
@@ -151,10 +155,14 @@ class DBInbound {
}
}
- genLink(clientIndex) {
+ genLink(clientIndex = 0) {
const inbound = this.toInbound();
return inbound.genLink(this.address, this.remark, clientIndex);
}
+ get genInboundLinks() {
+ const inbound = this.toInbound();
+ return inbound.genInboundLinks(this.address, this.remark);
+ }
}
class AllSetting {
diff --git a/web/assets/js/model/xray.js b/web/assets/js/model/xray.js
index 23d73930..ea34e081 100644
--- a/web/assets/js/model/xray.js
+++ b/web/assets/js/model/xray.js
@@ -101,6 +101,7 @@ Object.freeze(XTLS_FLOW_CONTROL);
Object.freeze(TLS_FLOW_CONTROL);
Object.freeze(TLS_VERSION_OPTION);
Object.freeze(TLS_CIPHER_OPTION);
+Object.freeze(UTLS_FINGERPRINT);
class XrayCommonClass {
@@ -1065,7 +1066,6 @@ class Inbound extends XrayCommonClass {
params.set("type", this.stream.network);
if (this.xtls) {
params.set("security", "xtls");
- address = this.stream.tls.server;
} else {
params.set("security", this.stream.security);
}
@@ -1119,7 +1119,10 @@ class Inbound extends XrayCommonClass {
address = this.stream.tls.server;
params.set("sni", address);
}
- params.set("flow", this.settings.vlesses[clientIndex].flow);
+ if (this.settings.vlesses[clientIndex].flow === "xtls-rprx-vision") {
+ params.set("flow", this.settings.vlesses[clientIndex].flow);
+ }
+ params.set("fp", this.settings.vlesses[clientIndex].fingerprint);
}
if (this.xtls) {
@@ -1135,7 +1138,7 @@ class Inbound extends XrayCommonClass {
return url.toString();
}
- genSSLink(address = '', remark = '') {
+ genSSLink(address = '', remark = '',clientIndex) {
let settings = this.settings;
const server = this.stream.tls.server;
if (!ObjectUtil.isEmpty(server)) {
@@ -1245,6 +1248,22 @@ class Inbound extends XrayCommonClass {
default: return '';
}
}
+ genInboundLinks(address = '', remark = '') {
+ let link = '';
+ JSON.parse(this.settings)
+ switch (this.protocol) {
+ case Protocols.VMESS:
+ case Protocols.VLESS:
+ case Protocols.TROJAN:
+ JSON.parse(this.settings).clients.forEach((client,index) => {
+ link += this.genLink(address, remark, index) + '\r\n';
+ });
+ return link;
+ case Protocols.SHADOWSOCKS:
+ return (this.genSSLink(address, remark) + '\r\n');
+ default: return '';
+ }
+}
static fromJson(json={}) {
return new Inbound(
@@ -1359,11 +1378,12 @@ Inbound.VmessSettings = class extends Inbound.Settings {
}
};
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
- constructor(id=RandomUtil.randomUUID(), alterId=0, email=RandomUtil.randomText(), totalGB=0, expiryTime='') {
+ constructor(id=RandomUtil.randomUUID(), alterId=0, email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime='') {
super();
this.id = id;
this.alterId = alterId;
this.email = email;
+ this.limitIp = limitIp;
this.totalGB = totalGB;
this.expiryTime = expiryTime;
}
@@ -1373,6 +1393,7 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
json.id,
json.alterId,
json.email,
+ json.limitIp,
json.totalGB,
json.expiryTime,
@@ -1441,11 +1462,12 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
};
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
- constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomText(), totalGB=0, fingerprint = UTLS_FINGERPRINT.UTLS_CHROME, expiryTime='') {
+ constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomText(),limitIp=0, totalGB=0, fingerprint = UTLS_FINGERPRINT.UTLS_CHROME, expiryTime='') {
super();
this.id = id;
this.flow = flow;
this.email = email;
+ this.limitIp = limitIp;
this.totalGB = totalGB;
this.fingerprint = fingerprint;
this.expiryTime = expiryTime;
@@ -1457,6 +1479,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
json.id,
json.flow,
json.email,
+ json.limitIp,
json.totalGB,
json.fingerprint,
json.expiryTime,
@@ -1557,11 +1580,12 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
}
};
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
- constructor(password=RandomUtil.randomSeq(10), flow ='', email=RandomUtil.randomText(), totalGB=0, expiryTime='') {
+ constructor(password=RandomUtil.randomSeq(10), flow ='', email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime='') {
super();
this.password = password;
this.flow = flow;
this.email = email;
+ this.limitIp = limitIp;
this.totalGB = totalGB;
this.expiryTime = expiryTime;
}
@@ -1571,6 +1595,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
password: this.password,
flow: this.flow,
email: this.email,
+ limitIp: this.limitIp,
totalGB: this.totalGB,
expiryTime: this.expiryTime,
};
@@ -1581,6 +1606,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
json.password,
json.flow,
json.email,
+ json.limitIp,
json.totalGB,
json.expiryTime,
diff --git a/web/controller/inbound.go b/web/controller/inbound.go
index 96c7ba18..8ec90ed4 100644
--- a/web/controller/inbound.go
+++ b/web/controller/inbound.go
@@ -31,6 +31,8 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
g.POST("/add", a.addInbound)
g.POST("/del/:id", a.delInbound)
g.POST("/update/:id", a.updateInbound)
+ g.POST("/clientIps/:email", a.getClientIps)
+ g.POST("/clearClientIps/:email", a.clearClientIps)
g.POST("/resetClientTraffic/:email", a.resetClientTraffic)
}
@@ -122,7 +124,26 @@ func (a *InboundController) updateInbound(c *gin.Context) {
a.xrayService.SetToNeedRestart()
}
}
+func (a *InboundController) getClientIps(c *gin.Context) {
+ email := c.Param("email")
+ ips , err := a.inboundService.GetInboundClientIps(email)
+ if err != nil {
+ jsonObj(c, "No IP Record", nil)
+ return
+ }
+ jsonObj(c, ips, nil)
+}
+func (a *InboundController) clearClientIps(c *gin.Context) {
+ email := c.Param("email")
+
+ err := a.inboundService.ClearClientIps(email)
+ if err != nil {
+ jsonMsg(c, "修改", err)
+ return
+ }
+ jsonMsg(c, "Log Cleared", nil)
+}
func (a *InboundController) resetClientTraffic(c *gin.Context) {
email := c.Param("email")
diff --git a/web/html/login.html b/web/html/login.html
index 5138f15e..f2c2116c 100644
--- a/web/html/login.html
+++ b/web/html/login.html
@@ -39,7 +39,7 @@
<a-layout-content>
<a-row type="flex" justify="center">
<a-col :xs="22" :sm="20" :md="16" :lg="12" :xl="8">
- <h1>{{ i18n "pages.login.title" }}</h1>
+ <h1>3x-ui {{ i18n "pages.login.title" }}</h1>
</a-col>
</a-row>
<a-row type="flex" justify="center">
diff --git a/web/html/xui/form/protocol/trojan.html b/web/html/xui/form/protocol/trojan.html
index 6d43bc34..4bf57d7a 100644
--- a/web/html/xui/form/protocol/trojan.html
+++ b/web/html/xui/form/protocol/trojan.html
@@ -22,6 +22,41 @@
</span>
<a-input v-model.trim="trojan.email"></a-input>
</a-form-item>
+ <a-form-item>
+ <span slot="label">
+ IP Count Limit
+ <a-tooltip>
+ <template slot="title">
+ disable inbound if more than entered count (0 for disable limit ip)
+ </template>
+ <a-icon type="question-circle" theme="filled"></a-icon>
+ </a-tooltip>
+ </span>
+ <a-input type="number" v-model.number="trojan.limitIp" min="0" ></a-input>
+ </a-form-item>
+ <a-form-item v-if="trojan.email && trojan.limitIp > 0 && isEdit">
+ <span slot="label">
+ IP log
+ <a-tooltip>
+ <template slot="title">
+ IPs history Log (before enabling inbound after it has been disabled by IP limit, you should clear the log)
+ </template>
+ <a-icon type="question-circle" theme="filled"></a-icon>
+ </a-tooltip>
+ <a-tooltip>
+ <template slot="title">
+ clear the log
+ </template>
+ <span style="color: #FF4D4F">
+ <a-icon type="delete" @click="clearDBClientIps(trojan.email,$event)"></a-icon>
+ </span>
+ </a-tooltip>
+ </span>
+ <a-form layout="block">
+ <a-textarea readonly @click="getDBClientIps(trojan.email,$event)" placeholder="Click To Get IPs" :auto-size="{ minRows: 0.5, maxRows: 10 }">
+ </a-textarea>
+ </a-form>
+ </a-form-item>
</a-form>
<a-form-item label="Password">
<a-input v-model.trim="trojan.password"></a-input>
diff --git a/web/html/xui/form/protocol/vless.html b/web/html/xui/form/protocol/vless.html
index fc7ffaa6..6d895f19 100644
--- a/web/html/xui/form/protocol/vless.html
+++ b/web/html/xui/form/protocol/vless.html
@@ -23,6 +23,42 @@
</span>
<a-input v-model.trim="vless.email"></a-input>
</a-form-item>
+ <a-form-item>
+ <span slot="label">
+ IP Count Limit
+ <a-tooltip>
+ <template slot="title">
+ disable inbound if more than entered count (0 for disable limit ip)
+ </template>
+ <a-icon type="question-circle" theme="filled"></a-icon>
+ </a-tooltip>
+ </span>
+ <a-input type="number" v-model.number="vless.limitIp" min="0" ></a-input>
+ </a-form-item>
+ <a-form-item v-if="vless.email && vless.limitIp > 0 && isEdit">
+ <span slot="label">
+ IP log
+ <a-tooltip>
+ <template slot="title">
+ IPs history Log (before enabling inbound after it has been disabled by IP limit, you should clear the log)
+ </template>
+ <a-icon type="question-circle" theme="filled"></a-icon>
+ </a-tooltip>
+ <a-tooltip>
+ <template slot="title">
+ clear the log
+ </template>
+ <span style="color: #FF4D4F">
+ <a-icon type="delete" @click="clearDBClientIps(vless.email,$event)"></a-icon>
+ </span>
+ </a-tooltip>
+ </span>
+ <a-form layout="block">
+
+ <a-textarea readonly @click="getDBClientIps(vless.email,$event)" placeholder="Click To Get IPs" :auto-size="{ minRows: 0.5, maxRows: 10 }">
+ </a-textarea>
+ </a-form>
+ </a-form-item>
</a-form>
<a-form-item label="ID">
<a-input v-model.trim="vless.id"></a-input>
diff --git a/web/html/xui/form/protocol/vmess.html b/web/html/xui/form/protocol/vmess.html
index f7050a5e..bab0cb8b 100644
--- a/web/html/xui/form/protocol/vmess.html
+++ b/web/html/xui/form/protocol/vmess.html
@@ -22,6 +22,39 @@
</span>
<a-input v-model.trim="vmess.email"></a-input>
</a-form-item>
+ <a-form-item>
+ <span slot="label">
+ IP Count Limit
+ <a-tooltip>
+ <template slot="title">
+ disable inbound if more than entered count (0 for disable limit ip)
+ </template>
+ <a-icon type="question-circle" theme="filled"></a-icon>
+ </a-tooltip>
+ </span>
+ <a-input type="number" v-model.number="vmess.limitIp" min="0" ></a-input>
+ </a-form-item>
+ <a-form-item v-if="vmess.email && vmess.limitIp > 0 && isEdit">
+ <span slot="label">
+ IP Log
+ <a-tooltip>
+ <template slot="title">
+ IPs history Log (before enabling inbound after it has been disabled by IP limit, you should clear the log)
+ </template>
+ <a-icon type="question-circle" theme="filled"></a-icon>
+ </a-tooltip>
+ <a-tooltip>
+ <template slot="title">
+ clear the log
+ </template>
+ <span style="color: #FF4D4F">
+ <a-icon type="delete" @click="clearDBClientIps(vmess.email,$event)"></a-icon>
+ </span>
+ </a-tooltip>
+ </span>
+ <a-textarea readonly @click="getDBClientIps(vmess.email,$event)" placeholder="Click To Get IPs" :auto-size="{ minRows: 0.5, maxRows: 10 }">
+ </a-textarea>
+ </a-form-item>
</a-form>
<a-form-item label="ID">
<a-input v-model.trim="vmess.id"></a-input>
diff --git a/web/html/xui/inbound_modal.html b/web/html/xui/inbound_modal.html
index 80ea2286..54a64bf9 100644
--- a/web/html/xui/inbound_modal.html
+++ b/web/html/xui/inbound_modal.html
@@ -88,6 +88,30 @@
removeClient(index, clients) {
clients.splice(index, 1);
},
+ async getDBClientIps(email,event) {
+
+ const msg = await HttpUtil.post('/xui/inbound/clientIps/'+ email);
+ if (!msg.success) {
+ return;
+ }
+ try {
+ ips = JSON.parse(msg.obj)
+ ips = ips.join(",")
+ event.target.value = ips
+ } catch (error) {
+ // text
+ event.target.value = msg.obj
+
+ }
+
+ },
+ async clearDBClientIps(email,event) {
+ const msg = await HttpUtil.post('/xui/inbound/clearClientIps/'+ email);
+ if (!msg.success) {
+ return;
+ }
+ event.target.value = ""
+ },
async resetClientTraffic(client,event) {
const msg = await HttpUtil.post('/xui/inbound/resetClientTraffic/'+ client.email);
if (!msg.success) {
diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html
index 14a61669..1f324846 100644
--- a/web/html/xui/inbounds.html
+++ b/web/html/xui/inbounds.html
@@ -45,6 +45,7 @@
<a-card hoverable>
<div slot="title">
<a-button type="primary" @click="openAddInbound">Add Inbound</a-button>
+ <a-button type="primary" @click="exportAllLinks" class="copy-btn">Export Links</a-button>
</div>
<a-input v-model.lazy="searchKey" placeholder="{{ i18n "search" }}" autofocus style="max-width: 300px"></a-input>
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
@@ -371,6 +372,18 @@
},
});
},
+ exportAllLinks() {
+ let copyText = '';
+ for (const dbInbound of this.dbInbounds) {
+ copyText += dbInbound.genInboundLinks
+ }
+ const clipboard = new ClipboardJS('.copy-btn', {
+ text: function () {
+ return copyText;
+ }
+ });
+ clipboard.on('success', () => { this.$message.success('Export Links succeed'); });
+ },
delInbound(dbInbound) {
this.$confirm({
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
diff --git a/web/job/check_clinet_ip_job.go b/web/job/check_clinet_ip_job.go
new file mode 100644
index 00000000..bf71116b
--- /dev/null
+++ b/web/job/check_clinet_ip_job.go
@@ -0,0 +1,351 @@
+package job
+
+import (
+ "x-ui/logger"
+ "x-ui/web/service"
+ "x-ui/database"
+ "x-ui/database/model"
+ "os"
+ ss "strings"
+ "regexp"
+ "encoding/json"
+ // "strconv"
+ "strings"
+ "time"
+ "net"
+ "github.com/go-cmd/cmd"
+ "sort"
+)
+
+type CheckClientIpJob struct {
+ xrayService service.XrayService
+ inboundService service.InboundService
+}
+var job *CheckClientIpJob
+var disAllowedIps []string
+
+func NewCheckClientIpJob() *CheckClientIpJob {
+ job = new(CheckClientIpJob)
+ return job
+}
+
+func (j *CheckClientIpJob) Run() {
+ logger.Debug("Check Client IP Job...")
+ processLogFile()
+
+ // disAllowedIps = []string{"192.168.1.183","192.168.1.197"}
+ blockedIps := []byte(ss.Join(disAllowedIps,","))
+ err := os.WriteFile("./bin/blockedIPs", blockedIps, 0755)
+ checkError(err)
+
+}
+
+func processLogFile() {
+ accessLogPath := GetAccessLogPath()
+ if(accessLogPath == "") {
+ logger.Warning("xray log not init in config.json")
+ return
+ }
+
+ data, err := os.ReadFile(accessLogPath)
+ InboundClientIps := make(map[string][]string)
+ checkError(err)
+
+ // clean log
+ if err := os.Truncate(GetAccessLogPath(), 0); err != nil {
+ checkError(err)
+ }
+
+ lines := ss.Split(string(data), "\n")
+ for _, line := range lines {
+ ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
+ emailRegx, _ := regexp.Compile(`email:.+`)
+
+ matchesIp := ipRegx.FindString(line)
+ if(len(matchesIp) > 0) {
+ ip := string(matchesIp)
+ if( ip == "127.0.0.1" || ip == "1.1.1.1") {
+ continue
+ }
+
+ matchesEmail := emailRegx.FindString(line)
+ if(matchesEmail == "") {
+ continue
+ }
+ matchesEmail = ss.Split(matchesEmail, "email: ")[1]
+
+ if(InboundClientIps[matchesEmail] != nil) {
+ if(contains(InboundClientIps[matchesEmail],ip)){
+ continue
+ }
+ InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail],ip)
+
+
+
+ }else{
+ InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail],ip)
+ }
+ }
+
+ }
+ disAllowedIps = []string{}
+
+ for clientEmail, ips := range InboundClientIps {
+ inboundClientIps,err := GetInboundClientIps(clientEmail)
+ sort.Sort(sort.StringSlice(ips))
+ if(err != nil){
+ addInboundClientIps(clientEmail,ips)
+
+ }else{
+ updateInboundClientIps(inboundClientIps,clientEmail,ips)
+ }
+
+ }
+
+
+ // check if inbound connection is more than limited ip and drop connection
+ LimitDevice := func() { LimitDevice() }
+
+ stop := schedule(LimitDevice, 1000 *time.Millisecond)
+ time.Sleep(10 * time.Second)
+ stop <- true
+
+}
+func GetAccessLogPath() string {
+
+ config, err := os.ReadFile("bin/config.json")
+ checkError(err)
+
+ jsonConfig := map[string]interface{}{}
+ err = json.Unmarshal([]byte(config), &jsonConfig)
+ checkError(err)
+ if(jsonConfig["log"] != nil) {
+ jsonLog := jsonConfig["log"].(map[string]interface{})
+ if(jsonLog["access"] != nil) {
+
+ accessLogPath := jsonLog["access"].(string)
+
+ return accessLogPath
+ }
+ }
+ return ""
+
+}
+func checkError(e error) {
+ if e != nil {
+ logger.Warning("client ip job err:", e)
+ }
+}
+func contains(s []string, str string) bool {
+ for _, v := range s {
+ if v == str {
+ return true
+ }
+ }
+
+ return false
+}
+func GetInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
+ db := database.GetDB()
+ InboundClientIps := &model.InboundClientIps{}
+ err := db.Model(model.InboundClientIps{}).Where("client_email = ?", clientEmail).First(InboundClientIps).Error
+ if err != nil {
+ return nil, err
+ }
+ return InboundClientIps, nil
+}
+func addInboundClientIps(clientEmail string,ips []string) error {
+ inboundClientIps := &model.InboundClientIps{}
+ jsonIps, err := json.Marshal(ips)
+ checkError(err)
+
+ inboundClientIps.ClientEmail = clientEmail
+ inboundClientIps.Ips = string(jsonIps)
+
+
+ db := database.GetDB()
+ tx := db.Begin()
+
+ defer func() {
+ if err == nil {
+ tx.Commit()
+ } else {
+ tx.Rollback()
+ }
+ }()
+
+ err = tx.Save(inboundClientIps).Error
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func updateInboundClientIps(inboundClientIps *model.InboundClientIps,clientEmail string,ips []string) error {
+
+ jsonIps, err := json.Marshal(ips)
+ checkError(err)
+
+ inboundClientIps.ClientEmail = clientEmail
+ inboundClientIps.Ips = string(jsonIps)
+
+ // check inbound limitation
+ inbound, err := GetInboundByEmail(clientEmail)
+ checkError(err)
+
+ if inbound.Settings == "" {
+ logger.Debug("wrong data ",inbound)
+ return nil
+ }
+
+ settings := map[string][]model.Client{}
+ json.Unmarshal([]byte(inbound.Settings), &settings)
+ clients := settings["clients"]
+
+ for _, client := range clients {
+ if client.Email == clientEmail {
+
+ limitIp := client.LimitIP
+
+ if(limitIp < len(ips) && limitIp != 0 && inbound.Enable) {
+
+ disAllowedIps = append(disAllowedIps,ips[limitIp:]...)
+ }
+ }
+ }
+ logger.Debug("disAllowedIps ",disAllowedIps)
+ sort.Sort(sort.StringSlice(disAllowedIps))
+
+ db := database.GetDB()
+ err = db.Save(inboundClientIps).Error
+ if err != nil {
+ return err
+ }
+ return nil
+}
+func DisableInbound(id int) error{
+ db := database.GetDB()
+ result := db.Model(model.Inbound{}).
+ Where("id = ? and enable = ?", id, true).
+ Update("enable", false)
+ err := result.Error
+ logger.Warning("disable inbound with id:",id)
+
+ if err == nil {
+ job.xrayService.SetToNeedRestart()
+ }
+
+ return err
+}
+
+func GetInboundByEmail(clientEmail string) (*model.Inbound, error) {
+ db := database.GetDB()
+ var inbounds *model.Inbound
+ err := db.Model(model.Inbound{}).Where("settings LIKE ?", "%" + clientEmail + "%").Find(&inbounds).Error
+ if err != nil {
+ return nil, err
+ }
+ return inbounds, nil
+}
+
+func LimitDevice(){
+
+ localIp,err := LocalIP()
+ checkError(err)
+
+ c := cmd.NewCmd("bash","-c","ss --tcp | grep -E '" + IPsToRegex(localIp) + "'| awk '{if($1==\"ESTAB\") print $4,$5;}'","| sort | uniq -c | sort -nr | head")
+
+ <-c.Start()
+ if len(c.Status().Stdout) > 0 {
+ ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
+ portRegx, _ := regexp.Compile(`(?:(:))([0-9]..[^.][0-9]+)`)
+
+ for _, row := range c.Status().Stdout {
+
+ data := strings.Split(row," ")
+
+ destIp,destPort,srcIp,srcPort := "","","",""
+
+
+ destIp = string(ipRegx.FindString(data[0]))
+
+ destPort = portRegx.FindString(data[0])
+ destPort = strings.Replace(destPort,":","",-1)
+
+
+ srcIp = string(ipRegx.FindString(data[1]))
+
+ srcPort = portRegx.FindString(data[1])
+ srcPort = strings.Replace(srcPort,":","",-1)
+
+ if(contains(disAllowedIps,srcIp)){
+ dropCmd := cmd.NewCmd("bash","-c","ss -K dport = " + srcPort)
+ dropCmd.Start()
+
+ logger.Debug("request droped : ",srcIp,srcPort,"to",destIp,destPort)
+ }
+ }
+ }
+
+}
+
+func LocalIP() ([]string, error) {
+ // get machine ips
+
+ ifaces, err := net.Interfaces()
+ ips := []string{}
+ if err != nil {
+ return ips, err
+ }
+ for _, i := range ifaces {
+ addrs, err := i.Addrs()
+ if err != nil {
+ return ips, err
+ }
+
+ for _, addr := range addrs {
+ var ip net.IP
+ switch v := addr.(type) {
+ case *net.IPNet:
+ ip = v.IP
+ case *net.IPAddr:
+ ip = v.IP
+ }
+
+ ips = append(ips,ip.String())
+
+ }
+ }
+ logger.Debug("System IPs : ",ips)
+
+ return ips, nil
+}
+
+
+func IPsToRegex(ips []string) (string){
+
+ regx := ""
+ for _, ip := range ips {
+ regx += "(" + strings.Replace(ip, ".", "\\.", -1) + ")"
+
+ }
+ regx = "(" + strings.Replace(regx, ")(", ")|(.", -1) + ")"
+
+ return regx
+}
+
+func schedule(LimitDevice func(), delay time.Duration) chan bool {
+ stop := make(chan bool)
+
+ go func() {
+ for {
+ LimitDevice()
+ select {
+ case <-time.After(delay):
+ case <-stop:
+ return
+ }
+ }
+ }()
+
+ return stop
+}
diff --git a/web/service/inbound.go b/web/service/inbound.go
index 5c1cfedc..450c77f0 100644
--- a/web/service/inbound.go
+++ b/web/service/inbound.go
@@ -369,7 +369,29 @@ func (s *InboundService) UpdateClientStat(inboundId int, inboundSettings string)
func (s *InboundService) DelClientStat(tx *gorm.DB, email string) error {
return tx.Where("email = ?", email).Delete(xray.ClientTraffic{}).Error
}
+func (s *InboundService) GetInboundClientIps(clientEmail string) (string, error) {
+ db := database.GetDB()
+ InboundClientIps := &model.InboundClientIps{}
+ err := db.Model(model.InboundClientIps{}).Where("client_email = ?", clientEmail).First(InboundClientIps).Error
+ if err != nil {
+ return "", err
+ }
+ return InboundClientIps.Ips, nil
+}
+func (s *InboundService) ClearClientIps(clientEmail string) (error) {
+ db := database.GetDB()
+
+ result := db.Model(model.InboundClientIps{}).
+ Where("client_email = ?", clientEmail).
+ Update("ips", "")
+ err := result.Error
+
+ if err != nil {
+ return err
+ }
+ return nil
+}
func (s *InboundService) ResetClientTraffic(clientEmail string) error {
db := database.GetDB()
diff --git a/web/service/server.go b/web/service/server.go
index c0a8e9d2..d12c8e27 100644
--- a/web/service/server.go
+++ b/web/service/server.go
@@ -172,7 +172,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
}
func (s *ServerService) GetXrayVersions() ([]string, error) {
- url := "https://api.github.com/repos/XTLS/Xray-core/releases"
+ url := "https://api.github.com/repos/mhsanaei/Xray-core/releases"
resp, err := http.Get(url)
if err != nil {
return nil, err
@@ -215,7 +215,7 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
}
fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
- url := fmt.Sprintf("https://github.com/XTLS/Xray-core/releases/download/%s/%s", version, fileName)
+ url := fmt.Sprintf("https://github.com/mhsanaei/Xray-core/releases/download/%s/%s", version, fileName)
resp, err := http.Get(url)
if err != nil {
return "", err
diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml
index 7d439fea..5751588b 100644
--- a/web/translation/translate.fa_IR.toml
+++ b/web/translation/translate.fa_IR.toml
@@ -54,7 +54,7 @@
"link" = "دیگر"
[pages.login]
-"title" = "ورود به سیستم X-UI"
+"title" = "ورود به سیستم"
"loginAgain" = "مدت زمان استفاده به اتمام رسیده ، لطفا دوباره وارد شوید"
[pages.login.toasts]
diff --git a/web/web.go b/web/web.go
index 7e5f73de..d57fc084 100644
--- a/web/web.go
+++ b/web/web.go
@@ -310,6 +310,9 @@ func (s *Server) startTask() {
// Check the inbound traffic every 30 seconds that the traffic exceeds and expires
s.cron.AddJob("@every 30s", job.NewCheckInboundJob())
+ // check client ips from log file every 10 sec
+ s.cron.AddJob("@every 10s", job.NewCheckClientIpJob())
+
// Make a traffic condition every day, 8:30
var entry cron.EntryID
isTgbotenabled, err := s.settingService.GetTgbotenabled()