This commit is contained in:
MHSanaei 2026-04-19 22:03:45 +02:00
parent 44ec6cf04c
commit 1bafa1fc37
No known key found for this signature in database
GPG key ID: 7E4060F2FBE5AB7A
2 changed files with 44 additions and 50 deletions

View file

@ -6,6 +6,7 @@ package iptables
import ( import (
"fmt" "fmt"
"net"
"os/exec" "os/exec"
"strconv" "strconv"
"strings" "strings"
@ -48,10 +49,23 @@ func FlushChain() error {
// BlockIP inserts a DROP rule for the given source IP on the given TCP destination // 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 // 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 { 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()) comment := fmt.Sprintf("3xui:block:%d", time.Now().Unix())
_, err := run("iptables", "-I", chain, _, err = run("iptables", "-I", chain,
"-s", ip, "-s", ip,
"-p", "tcp", "--dport", strconv.Itoa(port), "-p", "tcp", "--dport", strconv.Itoa(port),
"-m", "comment", "--comment", comment, "-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. // UnblockIP removes the DROP rule for the given source IP and TCP destination port.
func UnblockIP(ip string, port int) error { 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, _, err := run("iptables", "-D", chain,
"-s", ip, "-s", ip,
"-p", "tcp", "--dport", strconv.Itoa(port), "-p", "tcp", "--dport", strconv.Itoa(port),
@ -154,7 +171,7 @@ func findComment(ip string, port int) string {
if err != nil { if err != nil {
return "" return ""
} }
needle := fmt.Sprintf("-s %s", ip) needle := fmt.Sprintf("-s %s/32", ip)
dport := fmt.Sprintf("--dport %d", port) dport := fmt.Sprintf("--dport %d", port)
for _, line := range strings.Split(out, "\n") { for _, line := range strings.Split(out, "\n") {
if strings.Contains(line, needle) && strings.Contains(line, dport) { if strings.Contains(line, needle) && strings.Contains(line, dport) {

View file

@ -2532,19 +2532,16 @@ func (s *InboundService) DelInboundClientByEmail(inboundId int, email string) (b
return needRestart, db.Save(oldInbound).Error return needRestart, db.Save(oldInbound).Error
} }
// blockClientIPs inserts iptables DROP rules for all known IPs of the given client. // clientIPsForFirewall returns the list of known IP addresses and the inbound port for a client.
// Failures are logged as warnings so a missing iptables binary does not break the func (s *InboundService) clientIPsForFirewall(email string) ([]string, int) {
// normal disable flow.
func (s *InboundService) blockClientIPs(email string) {
ipsJSON, err := s.GetInboundClientIps(email) ipsJSON, err := s.GetInboundClientIps(email)
if err != nil || ipsJSON == "" { if err != nil || ipsJSON == "" {
return return nil, 0
} }
_, inbound, err := s.GetClientInboundByEmail(email) _, inbound, err := s.GetClientInboundByEmail(email)
if err != nil || inbound == nil { if err != nil || inbound == nil {
return return nil, 0
} }
port := inbound.Port
type IPWithTimestamp struct { type IPWithTimestamp struct {
IP string `json:"ip"` IP string `json:"ip"`
@ -2552,57 +2549,37 @@ func (s *InboundService) blockClientIPs(email string) {
} }
var ipsWithTime []IPWithTimestamp var ipsWithTime []IPWithTimestamp
if err := json.Unmarshal([]byte(ipsJSON), &ipsWithTime); err != nil { if err := json.Unmarshal([]byte(ipsJSON), &ipsWithTime); err != nil {
// Try simple string-array format
var simpleIPs []string var simpleIPs []string
if err2 := json.Unmarshal([]byte(ipsJSON), &simpleIPs); err2 != nil { if err2 := json.Unmarshal([]byte(ipsJSON), &simpleIPs); err2 != nil {
return return nil, 0
} }
for _, ip := range simpleIPs { return simpleIPs, inbound.Port
if berr := iptables.BlockIP(ip, port); berr != nil {
logger.Warning("blockClientIPs: failed to block", ip, berr)
}
}
return
} }
ips := make([]string, 0, len(ipsWithTime))
for _, entry := range ipsWithTime { for _, entry := range ipsWithTime {
if berr := iptables.BlockIP(entry.IP, port); berr != nil { ips = append(ips, entry.IP)
logger.Warning("blockClientIPs: failed to block", entry.IP, berr) }
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. // unblockClientIPs removes iptables DROP rules for all known IPs of the given client.
func (s *InboundService) unblockClientIPs(email string) { func (s *InboundService) unblockClientIPs(email string) {
ipsJSON, err := s.GetInboundClientIps(email) ips, port := s.clientIPsForFirewall(email)
if err != nil || ipsJSON == "" { for _, ip := range ips {
return if err := iptables.UnblockIP(ip, port); err != nil {
} logger.Debug("unblockClientIPs: failed to unblock", ip, err)
_, 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)
} }
} }
} }