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 (
"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) {

View file

@ -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)
}
}
}