From a3b170d6c49c5ce866e791bd5fdc867e0090dbfe Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami Date: Fri, 14 Apr 2023 00:07:13 +0430 Subject: update and rename client ip job file --- web/job/check_client_ip_job.go | 349 ++++++++++++++++++++++++++++++++++++++++ web/job/check_clinet_ip_job.go | 352 ----------------------------------------- 2 files changed, 349 insertions(+), 352 deletions(-) create mode 100644 web/job/check_client_ip_job.go delete mode 100644 web/job/check_clinet_ip_job.go diff --git a/web/job/check_client_ip_job.go b/web/job/check_client_ip_job.go new file mode 100644 index 00000000..95a652cd --- /dev/null +++ b/web/job/check_client_ip_job.go @@ -0,0 +1,349 @@ +package job + +import ( + "encoding/json" + "os" + "regexp" + ss "strings" + "x-ui/database" + "x-ui/database/model" + "x-ui/logger" + "x-ui/web/service" + "x-ui/xray" + // "strconv" + "github.com/go-cmd/cmd" + "net" + "sort" + "strings" + "time" +) + +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(xray.GetBlockedIPsPath(), 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(xray.GetConfigPath()) + 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) + + // Trim any leading/trailing whitespace from clientEmail + clientEmail = strings.TrimSpace(clientEmail) + + 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() { + var destIp, destPort, srcIp, srcPort string + + 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, " ") + + if len(data) < 2 { + continue // Skip this row if it doesn't have at least two elements + } + + 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/job/check_clinet_ip_job.go b/web/job/check_clinet_ip_job.go deleted file mode 100644 index ed86b42d..00000000 --- a/web/job/check_clinet_ip_job.go +++ /dev/null @@ -1,352 +0,0 @@ -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) - - // Trim any leading/trailing whitespace from clientEmail - clientEmail = strings.TrimSpace(clientEmail) - - 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() { - var destIp, destPort, srcIp, srcPort string - - 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," ") - - if len(data) < 2 { - continue // Skip this row if it doesn't have at least two elements - } - - 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 -} -- cgit v1.2.3