diff --git a/go.mod b/go.mod index 44b6570b6f..0342d7d24e 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/Workiva/go-datastructures v1.0.53 github.com/gin-contrib/sessions v0.0.3 github.com/gin-gonic/gin v1.7.1 + github.com/go-cmd/cmd v1.4.1 // indirect github.com/go-ole/go-ole v1.2.5 // indirect github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 github.com/nicksnyder/go-i18n/v2 v2.1.2 diff --git a/go.sum b/go.sum index c8ed240f17..9b808dd80f 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,8 @@ github.com/gin-gonic/gin v1.7.1 h1:qC89GU3p8TvKWMAVhEpmpB2CIb1hnqt2UdKZaP93mS8= github.com/gin-gonic/gin v1.7.1/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 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-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= @@ -72,6 +74,7 @@ github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7a github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 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/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= diff --git a/web/job/check_clinet_ip_job.go b/web/job/check_clinet_ip_job.go index 29e13a8703..e3396ad6eb 100644 --- a/web/job/check_clinet_ip_job.go +++ b/web/job/check_clinet_ip_job.go @@ -11,7 +11,11 @@ import ( "encoding/json" "gorm.io/gorm" "strconv" - + "strings" + "time" + "net" + "github.com/go-cmd/cmd" + "sort" ) type CheckClientIpJob struct { @@ -19,7 +23,8 @@ type CheckClientIpJob struct { inboundService service.InboundService } var job *CheckClientIpJob - +var disAllowedIps []string + func NewCheckClientIpJob() *CheckClientIpJob { job = new(CheckClientIpJob) return job @@ -84,6 +89,8 @@ func processLogFile() { } var inboundsClientIps []*model.InboundClientIps + disAllowedIps = []string{} + for clientEmail, ips := range InboundClientIps { inboundClientIps := GetInboundClientIps(clientEmail, ips) if inboundClientIps != nil { @@ -93,6 +100,14 @@ func processLogFile() { err = AddInboundsClientIps(inboundsClientIps) checkError(err) + + // check if inbound connection is more than limited ip and drop connection + LimitDevice := func() { LimitDevice() } + + stop := schedule(LimitDevice, 700 *time.Millisecond) + time.Sleep(60 * time.Second) + stop <- true + } func GetAccessLogPath() string { @@ -157,9 +172,17 @@ func GetInboundClientIps(clientEmail string, ips []string) *model.InboundClientI if err != nil { return nil } + if(limitIp < len(ips) && limitIp != 0 && inbound.Enable) { - DisableInbound(inbound.Id) + + if(limitIp == 1){ + limitIp = 2 + } + disAllowedIps = append(disAllowedIps,ips[limitIp - 1:]...) + } + logger.Debug("disAllowedIps ",disAllowedIps) + sort.Sort(sort.StringSlice(disAllowedIps)) return inboundClientIps } @@ -190,17 +213,118 @@ func GetInboundByEmail(clientEmail string) (*model.Inbound, error) { return inbounds, 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) +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") - if err == nil { - job.xrayService.SetToNeedRestart() + <-c.Start() + if len(c.Status().Stdout) > 0 { + + for _, row := range c.Status().Stdout { + + data := strings.Split(row," ") + + dest,src := strings.Split(data[0],":"),strings.Split(data[1],":") + + destIp,destPort := dest[0],dest[1] + srcIp,srcPort := src[0],src[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) + } + } } - return err +} + +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 + } + + if isPrivateIP(ip) { + ips = append(ips,ip.String()) + } + } + } + logger.Debug("System IPs : ",ips) + + return ips, nil +} + +func isPrivateIP(ip net.IP) bool { + var privateIPBlocks []*net.IPNet + for _, cidr := range []string{ + // don't check loopback ips + //"127.0.0.0/8", // IPv4 loopback + //"::1/128", // IPv6 loopback + //"fe80::/10", // IPv6 link-local + "10.0.0.0/8", // RFC1918 + "172.16.0.0/12", // RFC1918 + "192.168.0.0/16", // RFC1918 + } { + _, block, _ := net.ParseCIDR(cidr) + privateIPBlocks = append(privateIPBlocks, block) + } + + for _, block := range privateIPBlocks { + if block.Contains(ip) { + return true + } + } + + return false +} + +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/web.go b/web/web.go index 60a69da19c..0793a3900f 100644 --- a/web/web.go +++ b/web/web.go @@ -296,8 +296,8 @@ func (s *Server) startTask() { // 每 30 秒检查一次 inbound 流量超出和到期的情况 s.cron.AddJob("@every 30s", job.NewCheckInboundJob()) - // check client ips from log file every 1 min - s.cron.AddJob("@every 1m", job.NewCheckClientIpJob()) + // check client ips from log file every 30 sec + s.cron.AddJob("@every 30s", job.NewCheckClientIpJob()) // 每一天提示一次流量情况,上海时间8点30 var entry cron.EntryID