From a770cebb201e52008da073e94e1d38f4ccec3b88 Mon Sep 17 00:00:00 2001 From: Alireza Ahmadi Date: Thu, 11 Jun 2026 22:36:36 +0200 Subject: [PATCH] block user IPs on remove --- web/service/inbound.go | 28 +++++++++++++++++++++++++++- web/service/online_store.go | 11 +++++++++++ xray/api.go | 17 +++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/web/service/inbound.go b/web/service/inbound.go index cb4936dd..ad5d8322 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -613,9 +613,11 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool, } if needApiDel && notDepleted { s.xrayApi.Init(p.GetAPIAddr()) + onlineIPs := s.collectClientOnlineIPs(email) err1 := s.xrayApi.RemoveUser(oldInbound.Tag, email) if err1 == nil { logger.Debug("Client deleted by api:", email) + blockIPsForPort(onlineIPs, uint16(oldInbound.Port)) needRestart = false } else { if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", email)) { @@ -745,9 +747,14 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin if len(oldEmail) > 0 { s.xrayApi.Init(p.GetAPIAddr()) if oldClients[clientIndex].Enable { + var onlineIPs []string + if !clients[0].Enable { + onlineIPs = s.collectClientOnlineIPs(oldEmail) + } err1 := s.xrayApi.RemoveUser(oldInbound.Tag, oldEmail) if err1 == nil { logger.Debug("Old client deleted by api:", oldEmail) + blockIPsForPort(onlineIPs, uint16(oldInbound.Port)) } else { if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", oldEmail)) { logger.Debug("User is already deleted. Nothing to do more...") @@ -1137,6 +1144,22 @@ func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error return needRestart, count, err } +func (s *InboundService) collectClientOnlineIPs(email string) []string { + if ipLimitFw == nil || !ipLimitFw.Supported() { + return nil + } + ipMap, err := s.xrayApi.GetUserOnlineIpList(email) + if err != nil { + logger.Debug("get online ip list failed for ", email, ": ", err) + return nil + } + ips := make([]string, 0, len(ipMap)) + for ip := range ipMap { + ips = append(ips, ip) + } + return ips +} + func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, error) { now := time.Now().Unix() * 1000 needRestart := false @@ -1144,11 +1167,12 @@ func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, error) if p != nil { var results []struct { Tag string + Port int Email string } err := tx.Table("inbounds"). - Select("inbounds.tag, client_traffics.email"). + Select("inbounds.tag, inbounds.port, client_traffics.email"). Joins("JOIN client_traffics ON inbounds.id = client_traffics.inbound_id"). Where("((client_traffics.total > 0 AND client_traffics.up + client_traffics.down >= client_traffics.total) OR (client_traffics.expiry_time > 0 AND client_traffics.expiry_time <= ?)) AND client_traffics.enable = ?", now, true). Scan(&results).Error @@ -1157,9 +1181,11 @@ func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, error) } s.xrayApi.Init(p.GetAPIAddr()) for _, result := range results { + onlineIPs := s.collectClientOnlineIPs(result.Email) err1 := s.xrayApi.RemoveUser(result.Tag, result.Email) if err1 == nil { logger.Debug("Client disabled by api:", result.Email) + blockIPsForPort(onlineIPs, uint16(result.Port)) } else { if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", result.Email)) { logger.Debug("User is already disabled. Nothing to do more...") diff --git a/web/service/online_store.go b/web/service/online_store.go index a6d09968..9ff0786a 100644 --- a/web/service/online_store.go +++ b/web/service/online_store.go @@ -200,6 +200,17 @@ func (s *InboundService) syncIpLimitStore(updates []IpLimitClientUpdate, removeE applyIpLimitMemoryChanges(updates, removeEmails) } +func blockIPsForPort(ips []string, port uint16) { + if len(ips) == 0 || ipLimitFw == nil || !ipLimitFw.Supported() { + return + } + for _, ip := range ips { + if err := ipLimitFw.Block(iplimit.BlockKey{IP: ip, Port: port}); err != nil { + logger.Debug("block ip failed:", err) + } + } +} + func isClientStatEnabled(inbound *model.Inbound, email string) bool { for _, stat := range inbound.ClientStats { if stat.Email == email { diff --git a/xray/api.go b/xray/api.go index dbed1b38..d804e89d 100644 --- a/xray/api.go +++ b/xray/api.go @@ -283,6 +283,23 @@ func (x *XrayAPI) RemoveUser(inboundTag string, email string) error { return err } +func (x *XrayAPI) GetUserOnlineIpList(email string) (map[string]int64, error) { + if x.StatsServiceClient == nil { + return nil, common.NewError("xray api is not initialized") + } + client := *x.StatsServiceClient + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + + resp, err := client.GetStatsOnlineIpList(ctx, &statsService.GetStatsRequest{ + Name: "user>>>" + email + ">>>online", + }) + if err != nil { + return nil, err + } + return resp.GetIps(), nil +} + type OnlineUserInfo struct { Email string `json:"email"` IPs map[string]int64 `json:"ips"`