check_client_ip_job.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. package job
  2. import (
  3. "encoding/json"
  4. "log"
  5. "os"
  6. "regexp"
  7. "sort"
  8. "strings"
  9. "time"
  10. "x-ui/database"
  11. "x-ui/database/model"
  12. "x-ui/logger"
  13. "x-ui/xray"
  14. )
  15. type CheckClientIpJob struct {}
  16. var job *CheckClientIpJob
  17. var disAllowedIps []string
  18. var ipFiles = []string{
  19. xray.GetIPLimitLogPath(),
  20. xray.GetIPLimitBannedLogPath(),
  21. xray.GetAccessPersistentLogPath(),
  22. }
  23. func NewCheckClientIpJob() *CheckClientIpJob {
  24. job = new(CheckClientIpJob)
  25. return job
  26. }
  27. func (j *CheckClientIpJob) Run() {
  28. logger.Debug("Check Client IP Job...")
  29. // create files required for iplimit if not exists
  30. for i := 0; i < len(ipFiles); i++ {
  31. file, err := os.OpenFile(ipFiles[i], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
  32. j.checkError(err)
  33. defer file.Close()
  34. }
  35. // check for limit ip
  36. if j.hasLimitIp() {
  37. j.processLogFile()
  38. }
  39. }
  40. func (j *CheckClientIpJob) hasLimitIp() bool {
  41. db := database.GetDB()
  42. var inbounds []*model.Inbound
  43. err := db.Model(model.Inbound{}).Find(&inbounds).Error
  44. if err != nil {
  45. return false
  46. }
  47. for _, inbound := range inbounds {
  48. if inbound.Settings == "" {
  49. continue
  50. }
  51. settings := map[string][]model.Client{}
  52. json.Unmarshal([]byte(inbound.Settings), &settings)
  53. clients := settings["clients"]
  54. for _, client := range clients {
  55. limitIp := client.LimitIP
  56. if limitIp > 0 {
  57. return true
  58. }
  59. }
  60. }
  61. return false
  62. }
  63. func (j *CheckClientIpJob) processLogFile() {
  64. accessLogPath := xray.GetAccessLogPath()
  65. if accessLogPath == "" {
  66. logger.Warning("access.log doesn't exist in your config.json")
  67. return
  68. }
  69. data, err := os.ReadFile(accessLogPath)
  70. InboundClientIps := make(map[string][]string)
  71. j.checkError(err)
  72. lines := strings.Split(string(data), "\n")
  73. for _, line := range lines {
  74. ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
  75. emailRegx, _ := regexp.Compile(`email:.+`)
  76. matchesIp := ipRegx.FindString(line)
  77. if len(matchesIp) > 0 {
  78. ip := string(matchesIp)
  79. if ip == "127.0.0.1" || ip == "1.1.1.1" {
  80. continue
  81. }
  82. matchesEmail := emailRegx.FindString(line)
  83. if matchesEmail == "" {
  84. continue
  85. }
  86. matchesEmail = strings.TrimSpace(strings.Split(matchesEmail, "email: ")[1])
  87. if InboundClientIps[matchesEmail] != nil {
  88. if j.contains(InboundClientIps[matchesEmail], ip) {
  89. continue
  90. }
  91. InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
  92. } else {
  93. InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
  94. }
  95. }
  96. }
  97. disAllowedIps = []string{}
  98. shouldCleanLog := false
  99. for clientEmail, ips := range InboundClientIps {
  100. inboundClientIps, err := j.getInboundClientIps(clientEmail)
  101. sort.Strings(ips)
  102. if err != nil {
  103. j.addInboundClientIps(clientEmail, ips)
  104. } else {
  105. shouldCleanLog = j.updateInboundClientIps(inboundClientIps, clientEmail, ips)
  106. }
  107. }
  108. // added 3 seconds delay before cleaning logs to reduce chance of logging IP that already has been banned
  109. time.Sleep(time.Second * 3)
  110. if shouldCleanLog {
  111. // copy access log to persistent file
  112. logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
  113. j.checkError(err)
  114. input, err := os.ReadFile(accessLogPath)
  115. j.checkError(err)
  116. if _, err := logAccessP.Write(input); err != nil {
  117. j.checkError(err)
  118. }
  119. defer logAccessP.Close()
  120. // clean access log
  121. if err := os.Truncate(xray.GetAccessLogPath(), 0); err != nil {
  122. j.checkError(err)
  123. }
  124. }
  125. }
  126. func (j *CheckClientIpJob) checkError(e error) {
  127. if e != nil {
  128. logger.Warning("client ip job err:", e)
  129. }
  130. }
  131. func (j *CheckClientIpJob) contains(s []string, str string) bool {
  132. for _, v := range s {
  133. if v == str {
  134. return true
  135. }
  136. }
  137. return false
  138. }
  139. func (j *CheckClientIpJob) getInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
  140. db := database.GetDB()
  141. InboundClientIps := &model.InboundClientIps{}
  142. err := db.Model(model.InboundClientIps{}).Where("client_email = ?", clientEmail).First(InboundClientIps).Error
  143. if err != nil {
  144. return nil, err
  145. }
  146. return InboundClientIps, nil
  147. }
  148. func (j *CheckClientIpJob) addInboundClientIps(clientEmail string, ips []string) error {
  149. inboundClientIps := &model.InboundClientIps{}
  150. jsonIps, err := json.Marshal(ips)
  151. j.checkError(err)
  152. inboundClientIps.ClientEmail = clientEmail
  153. inboundClientIps.Ips = string(jsonIps)
  154. db := database.GetDB()
  155. tx := db.Begin()
  156. defer func() {
  157. if err == nil {
  158. tx.Commit()
  159. } else {
  160. tx.Rollback()
  161. }
  162. }()
  163. err = tx.Save(inboundClientIps).Error
  164. if err != nil {
  165. return err
  166. }
  167. return nil
  168. }
  169. func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmail string, ips []string) bool {
  170. jsonIps, err := json.Marshal(ips)
  171. j.checkError(err)
  172. inboundClientIps.ClientEmail = clientEmail
  173. inboundClientIps.Ips = string(jsonIps)
  174. // check inbound limitation
  175. inbound, err := j.getInboundByEmail(clientEmail)
  176. j.checkError(err)
  177. if inbound.Settings == "" {
  178. logger.Debug("wrong data ", inbound)
  179. return false
  180. }
  181. settings := map[string][]model.Client{}
  182. json.Unmarshal([]byte(inbound.Settings), &settings)
  183. clients := settings["clients"]
  184. shouldCleanLog := false
  185. // create iplimit log file channel
  186. logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
  187. if err != nil {
  188. logger.Errorf("failed to create or open ip limit log file: %s", err)
  189. }
  190. defer logIpFile.Close()
  191. log.SetOutput(logIpFile)
  192. log.SetFlags(log.LstdFlags)
  193. for _, client := range clients {
  194. if client.Email == clientEmail {
  195. limitIp := client.LimitIP
  196. if limitIp != 0 {
  197. shouldCleanLog = true
  198. if limitIp < len(ips) && inbound.Enable {
  199. disAllowedIps = append(disAllowedIps, ips[limitIp:]...)
  200. for i := limitIp; i < len(ips); i++ {
  201. log.Printf("[LIMIT_IP] Email = %s || SRC = %s", clientEmail, ips[i])
  202. }
  203. }
  204. }
  205. }
  206. }
  207. logger.Debug("disAllowedIps ", disAllowedIps)
  208. sort.Strings(disAllowedIps)
  209. db := database.GetDB()
  210. err = db.Save(inboundClientIps).Error
  211. if err != nil {
  212. return shouldCleanLog
  213. }
  214. return shouldCleanLog
  215. }
  216. func (j *CheckClientIpJob) getInboundByEmail(clientEmail string) (*model.Inbound, error) {
  217. db := database.GetDB()
  218. var inbounds *model.Inbound
  219. err := db.Model(model.Inbound{}).Where("settings LIKE ?", "%"+clientEmail+"%").Find(&inbounds).Error
  220. if err != nil {
  221. return nil, err
  222. }
  223. return inbounds, nil
  224. }