From 1bafa1fc3759762e0920056cdc91ae97e0a49d1f Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 19 Apr 2026 22:03:45 +0200 Subject: [PATCH] fix --- util/iptables/iptables.go | 23 +++++++++++-- web/service/inbound.go | 71 +++++++++++++-------------------------- 2 files changed, 44 insertions(+), 50 deletions(-) diff --git a/util/iptables/iptables.go b/util/iptables/iptables.go index b9a2038c..7eb8a876 100644 --- a/util/iptables/iptables.go +++ b/util/iptables/iptables.go @@ -6,6 +6,7 @@ package iptables import ( "fmt" + "net" "os/exec" "strconv" "strings" @@ -48,10 +49,23 @@ func FlushChain() error { // BlockIP inserts a DROP rule for the given source IP on the given TCP destination // port into the custom chain. The comment embeds the current Unix timestamp so -// the rule can be age-checked later. +// the rule can be age-checked later. Duplicate rules are skipped. func BlockIP(ip string, port int) error { + if net.ParseIP(ip) == nil { + return fmt.Errorf("invalid IP address: %s", ip) + } + + // Skip if an identical rule already exists + _, err := run("iptables", "-C", chain, + "-s", ip, + "-p", "tcp", "--dport", strconv.Itoa(port), + "-j", "DROP") + if err == nil { + return nil + } + comment := fmt.Sprintf("3xui:block:%d", time.Now().Unix()) - _, err := run("iptables", "-I", chain, + _, err = run("iptables", "-I", chain, "-s", ip, "-p", "tcp", "--dport", strconv.Itoa(port), "-m", "comment", "--comment", comment, @@ -64,6 +78,9 @@ func BlockIP(ip string, port int) error { // UnblockIP removes the DROP rule for the given source IP and TCP destination port. func UnblockIP(ip string, port int) error { + if net.ParseIP(ip) == nil { + return fmt.Errorf("invalid IP address: %s", ip) + } _, err := run("iptables", "-D", chain, "-s", ip, "-p", "tcp", "--dport", strconv.Itoa(port), @@ -154,7 +171,7 @@ func findComment(ip string, port int) string { if err != nil { return "" } - needle := fmt.Sprintf("-s %s", ip) + needle := fmt.Sprintf("-s %s/32", ip) dport := fmt.Sprintf("--dport %d", port) for _, line := range strings.Split(out, "\n") { if strings.Contains(line, needle) && strings.Contains(line, dport) { diff --git a/web/service/inbound.go b/web/service/inbound.go index 1485a74a..b7b72559 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -2532,19 +2532,16 @@ func (s *InboundService) DelInboundClientByEmail(inboundId int, email string) (b return needRestart, db.Save(oldInbound).Error } -// blockClientIPs inserts iptables DROP rules for all known IPs of the given client. -// Failures are logged as warnings so a missing iptables binary does not break the -// normal disable flow. -func (s *InboundService) blockClientIPs(email string) { +// clientIPsForFirewall returns the list of known IP addresses and the inbound port for a client. +func (s *InboundService) clientIPsForFirewall(email string) ([]string, int) { ipsJSON, err := s.GetInboundClientIps(email) if err != nil || ipsJSON == "" { - return + return nil, 0 } _, inbound, err := s.GetClientInboundByEmail(email) if err != nil || inbound == nil { - return + return nil, 0 } - port := inbound.Port type IPWithTimestamp struct { IP string `json:"ip"` @@ -2552,57 +2549,37 @@ func (s *InboundService) blockClientIPs(email string) { } var ipsWithTime []IPWithTimestamp if err := json.Unmarshal([]byte(ipsJSON), &ipsWithTime); err != nil { - // Try simple string-array format var simpleIPs []string if err2 := json.Unmarshal([]byte(ipsJSON), &simpleIPs); err2 != nil { - return + return nil, 0 } - for _, ip := range simpleIPs { - if berr := iptables.BlockIP(ip, port); berr != nil { - logger.Warning("blockClientIPs: failed to block", ip, berr) - } - } - return + return simpleIPs, inbound.Port } + ips := make([]string, 0, len(ipsWithTime)) for _, entry := range ipsWithTime { - if berr := iptables.BlockIP(entry.IP, port); berr != nil { - logger.Warning("blockClientIPs: failed to block", entry.IP, berr) + ips = append(ips, entry.IP) + } + return ips, inbound.Port +} + +// blockClientIPs inserts iptables DROP rules for all known IPs of the given client. +// Failures are logged as warnings so a missing iptables binary does not break the +// normal disable flow. +func (s *InboundService) blockClientIPs(email string) { + ips, port := s.clientIPsForFirewall(email) + for _, ip := range ips { + if err := iptables.BlockIP(ip, port); err != nil { + logger.Warning("blockClientIPs: failed to block", ip, err) } } } // unblockClientIPs removes iptables DROP rules for all known IPs of the given client. func (s *InboundService) unblockClientIPs(email string) { - ipsJSON, err := s.GetInboundClientIps(email) - if err != nil || ipsJSON == "" { - return - } - _, inbound, err := s.GetClientInboundByEmail(email) - if err != nil || inbound == nil { - return - } - port := inbound.Port - - type IPWithTimestamp struct { - IP string `json:"ip"` - Timestamp int64 `json:"timestamp"` - } - var ipsWithTime []IPWithTimestamp - if err := json.Unmarshal([]byte(ipsJSON), &ipsWithTime); err != nil { - var simpleIPs []string - if err2 := json.Unmarshal([]byte(ipsJSON), &simpleIPs); err2 != nil { - return - } - for _, ip := range simpleIPs { - if uerr := iptables.UnblockIP(ip, port); uerr != nil { - logger.Debug("unblockClientIPs: failed to unblock", ip, uerr) - } - } - return - } - for _, entry := range ipsWithTime { - if uerr := iptables.UnblockIP(entry.IP, port); uerr != nil { - logger.Debug("unblockClientIPs: failed to unblock", entry.IP, uerr) + ips, port := s.clientIPsForFirewall(email) + for _, ip := range ips { + if err := iptables.UnblockIP(ip, port); err != nil { + logger.Debug("unblockClientIPs: failed to unblock", ip, err) } } }