diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a054524f..fb74bfdc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: mv xui-release x-ui mkdir bin cd bin - wget https://github.com/XTLS/Xray-core/releases/latest/download/Xray-linux-64.zip + wget https://github.com/mhsanaei/Xray-core/releases/latest/download/Xray-linux-64.zip unzip Xray-linux-64.zip rm -f Xray-linux-64.zip geoip.dat geosite.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat @@ -40,3 +40,4 @@ jobs: file: x-ui-linux-amd64.tar.gz asset_name: x-ui-linux-amd64.tar.gz prerelease: true + overwrite: true diff --git a/database/db.go b/database/db.go index 15ff0e65..f7a590b2 100644 --- a/database/db.go +++ b/database/db.go @@ -42,6 +42,9 @@ func initInbound() error { func initSetting() error { return db.AutoMigrate(&model.Setting{}) } +func initInboundClientIps() error { + return db.AutoMigrate(&model.InboundClientIps{}) +} func initClientTraffic() error { return db.AutoMigrate(&xray.ClientTraffic{}) } @@ -81,6 +84,10 @@ func InitDB(dbPath string) error { if err != nil { return err } + err = initInboundClientIps() + if err != nil { + return err + } err = initClientTraffic() if err != nil { return err diff --git a/database/model/model.go b/database/model/model.go index 25e0cd41..f0036bdb 100644 --- a/database/model/model.go +++ b/database/model/model.go @@ -43,6 +43,11 @@ type Inbound struct { Tag string `json:"tag" form:"tag" gorm:"unique"` Sniffing string `json:"sniffing" form:"sniffing"` } +type InboundClientIps struct { + Id int `json:"id" gorm:"primaryKey;autoIncrement"` + ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"` + Ips string `json:"ips" form:"ips"` +} func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig { listen := i.Listen @@ -70,6 +75,7 @@ type Client struct { ID string `json:"id"` AlterIds uint16 `json:"alterId"` Email string `json:"email"` + LimitIP int `json:"limitIp"` Security string `json:"security"` TotalGB int64 `json:"totalGB" form:"totalGB"` ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` diff --git a/go.mod b/go.mod index 5816e0e9..da538fed 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/Workiva/go-datastructures v1.0.53 github.com/gin-contrib/sessions v0.0.4 github.com/gin-gonic/gin v1.9.0 + github.com/go-cmd/cmd v1.4.1 github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 github.com/nicksnyder/go-i18n/v2 v2.2.1 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 diff --git a/go.sum b/go.sum index 4d703900..2479e119 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,8 @@ github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjX github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-cmd/cmd v1.4.1 h1:JUcEIE84v8DSy02XTZpUDeGKExk2oW3DA10hTjbQwmc= +github.com/go-cmd/cmd v1.4.1/go.mod h1:tbBenttXtZU4c5djS1o7PWL5pd2xAr5sIqH1kGdNiRc= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -44,6 +46,7 @@ github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVL github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= +github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= 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 @@ -

{{ i18n "pages.login.title" }}

+

3x-ui {{ i18n "pages.login.title" }}

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 @@ -21,6 +21,41 @@ + + + + IP Count Limit + + + + + + + + + + IP log + + + + + + + + + + + + + + + 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 @@ -22,6 +22,42 @@ + + + + IP Count Limit + + + + + + + + + + IP log + + + + + + + + + + + + + + + + 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 @@ -21,6 +21,39 @@ + + + + IP Count Limit + + + + + + + + + + IP Log + + + + + + + + + + + + + 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 @@
Add Inbound + Export Links
{ 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() diff --git a/xray/process.go b/xray/process.go index cf894de8..a73ff95f 100644 --- a/xray/process.go +++ b/xray/process.go @@ -162,7 +162,7 @@ func (p *process) Start() (err error) { return common.NewErrorf("Failed to write configuration file: %v", err) } - cmd := exec.Command(GetBinaryPath(), "-c", configPath) + cmd := exec.Command(GetBinaryPath(), "-c", configPath, "-restrictedIPsPath", "./bin/blockedIPs") p.cmd = cmd stdReader, err := cmd.StdoutPipe()