Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
R
rpcproxy
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
vicotor
rpcproxy
Commits
1a73d9b8
Commit
1a73d9b8
authored
Dec 29, 2025
by
vicotor
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add ip blacklist
parent
c833b20f
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
196 additions
and
6 deletions
+196
-6
README.md
README.md
+16
-0
main.go
main.go
+180
-6
No files found.
README.md
View file @
1a73d9b8
...
@@ -32,6 +32,12 @@ Applies to IPs that frequently exceed the rate limit.
...
@@ -32,6 +32,12 @@ Applies to IPs that frequently exceed the rate limit.
-
`PENALTY_DURATION`
: The duration an IP stays in penalty mode (default:
`20m`
).
-
`PENALTY_DURATION`
: The duration an IP stays in penalty mode (default:
`20m`
).
-
`PENALTY_TRIGGER_COUNT`
: The number of consecutive rate limit violations required to trigger penalty mode (default:
`5`
).
-
`PENALTY_TRIGGER_COUNT`
: The number of consecutive rate limit violations required to trigger penalty mode (default:
`5`
).
### IP Blacklist Configuration
You can specify a file containing a list of IP addresses to be blacklisted. IPs in this list will be automatically placed in
**Penalty Mode**
.
-
`IP_BLACKLIST_FILE`
: The path to the file containing blacklisted IP addresses or domains.
## Usage
## Usage
1.
Set the required environment variables.
1.
Set the required environment variables.
...
@@ -50,3 +56,13 @@ example.com
...
@@ -50,3 +56,13 @@ example.com
*.example.com
*.example.com
```
```
## IP Blacklist File Format
The IP blacklist file follows the same format as the whitelist file.
Example:
```
192.168.1.100
bad-actor.com
*.botnet.net
```
main.go
View file @
1a73d9b8
...
@@ -79,6 +79,12 @@ var (
...
@@ -79,6 +79,12 @@ var (
whitelistPatterns
[]
*
regexp
.
Regexp
whitelistPatterns
[]
*
regexp
.
Regexp
whitelistMu
sync
.
RWMutex
whitelistMu
sync
.
RWMutex
// IP Blacklist related
ipBlacklistFile
string
ipBlacklist
map
[
string
]
struct
{}
ipBlacklistPatterns
[]
*
regexp
.
Regexp
ipBlacklistMu
sync
.
RWMutex
// Rate limiting
// Rate limiting
visitors
=
make
(
map
[
string
]
*
visitor
)
visitors
=
make
(
map
[
string
]
*
visitor
)
visitorsMu
sync
.
Mutex
visitorsMu
sync
.
Mutex
...
@@ -93,11 +99,12 @@ var (
...
@@ -93,11 +99,12 @@ var (
)
)
type
visitor
struct
{
type
visitor
struct
{
limiter
*
rate
.
Limiter
limiter
*
rate
.
Limiter
lastSeen
time
.
Time
lastSeen
time
.
Time
punishedUntil
time
.
Time
punishedUntil
time
.
Time
blockedCount
int
blockedCount
int
lastBlockTime
time
.
Time
lastBlockTime
time
.
Time
isStaticBlacklisted
bool
}
}
func
main
()
{
func
main
()
{
...
@@ -149,6 +156,22 @@ func main() {
...
@@ -149,6 +156,22 @@ func main() {
log
.
Printf
(
"WHITELIST_FILE not set, whitelist feature disabled"
)
log
.
Printf
(
"WHITELIST_FILE not set, whitelist feature disabled"
)
}
}
// Load IP blacklist file and start watcher
ipBlacklistFile
=
os
.
Getenv
(
"IP_BLACKLIST_FILE"
)
if
ipBlacklistFile
!=
""
{
blExact
,
blPatterns
:=
loadIPBlacklist
(
ipBlacklistFile
)
ipBlacklistMu
.
Lock
()
ipBlacklist
=
blExact
ipBlacklistPatterns
=
blPatterns
ipBlacklistMu
.
Unlock
()
log
.
Printf
(
"loaded ip blacklist entries: %d exact, %d patterns"
,
len
(
blExact
),
len
(
blPatterns
))
startIPBlacklistWatcher
(
ipBlacklistFile
)
}
else
{
ipBlacklist
=
map
[
string
]
struct
{}{}
ipBlacklistPatterns
=
[]
*
regexp
.
Regexp
{}
log
.
Printf
(
"IP_BLACKLIST_FILE not set, ip blacklist feature disabled"
)
}
// Start cache janitor for blacklist cache. Interval can be configured via env BLACKLIST_CACHE_CLEANUP_INTERVAL (e.g. "5m").
// Start cache janitor for blacklist cache. Interval can be configured via env BLACKLIST_CACHE_CLEANUP_INTERVAL (e.g. "5m").
cleanupInterval
:=
5
*
time
.
Minute
cleanupInterval
:=
5
*
time
.
Minute
if
s
:=
os
.
Getenv
(
"BLACKLIST_CACHE_CLEANUP_INTERVAL"
);
s
!=
""
{
if
s
:=
os
.
Getenv
(
"BLACKLIST_CACHE_CLEANUP_INTERVAL"
);
s
!=
""
{
...
@@ -643,6 +666,131 @@ func startWhitelistWatcher(path string) {
...
@@ -643,6 +666,131 @@ func startWhitelistWatcher(path string) {
}()
}()
}
}
// ===== IP Blacklist helper functions (dynamic reload) =====
func
loadIPBlacklist
(
path
string
)
(
map
[
string
]
struct
{},
[]
*
regexp
.
Regexp
)
{
result
:=
make
(
map
[
string
]
struct
{})
patterns
:=
make
([]
*
regexp
.
Regexp
,
0
)
f
,
err
:=
os
.
Open
(
path
)
if
err
!=
nil
{
log
.
Printf
(
"open ip blacklist file '%s' error: %v"
,
path
,
err
)
return
result
,
patterns
}
defer
f
.
Close
()
scanner
:=
bufio
.
NewScanner
(
f
)
lineNum
:=
0
for
scanner
.
Scan
()
{
lineNum
++
line
:=
strings
.
TrimSpace
(
scanner
.
Text
())
if
line
==
""
||
strings
.
HasPrefix
(
line
,
"#"
)
||
strings
.
HasPrefix
(
line
,
"//"
)
{
continue
}
// Determine if wildcard pattern present
if
strings
.
Contains
(
line
,
"*"
)
{
if
re
:=
compilePattern
(
line
);
re
!=
nil
{
patterns
=
append
(
patterns
,
re
)
}
else
{
log
.
Printf
(
"skip invalid pattern at line %d: %s"
,
lineNum
,
line
)
}
continue
}
result
[
line
]
=
struct
{}{}
}
if
err
:=
scanner
.
Err
();
err
!=
nil
{
log
.
Printf
(
"scan ip blacklist file error: %v"
,
err
)
}
return
result
,
patterns
}
func
isIPBlacklisted
(
v
string
)
bool
{
if
v
==
""
{
return
false
}
val
:=
strings
.
TrimSpace
(
v
)
if
val
==
""
{
return
false
}
// Collect candidate forms: raw, origin base (scheme://host), host (strip port), host:port
candidates
:=
make
([]
string
,
0
,
4
)
candidates
=
append
(
candidates
,
val
)
if
u
,
err
:=
url
.
Parse
(
val
);
err
==
nil
&&
u
.
Host
!=
""
{
host
:=
u
.
Host
// strip port for host-only
if
strings
.
Contains
(
host
,
":"
)
{
parts
:=
strings
.
Split
(
host
,
":"
)
hostNoPort
:=
parts
[
0
]
candidates
=
append
(
candidates
,
hostNoPort
)
}
candidates
=
append
(
candidates
,
host
)
base
:=
fmt
.
Sprintf
(
"%s://%s"
,
u
.
Scheme
,
host
)
candidates
=
append
(
candidates
,
base
)
}
ipBlacklistMu
.
RLock
()
defer
ipBlacklistMu
.
RUnlock
()
for
_
,
c
:=
range
candidates
{
if
_
,
ok
:=
ipBlacklist
[
c
];
ok
{
return
true
}
}
// pattern matching
for
_
,
re
:=
range
ipBlacklistPatterns
{
for
_
,
c
:=
range
candidates
{
if
re
.
MatchString
(
c
)
{
return
true
}
}
}
return
false
}
func
startIPBlacklistWatcher
(
path
string
)
{
watcher
,
err
:=
fsnotify
.
NewWatcher
()
if
err
!=
nil
{
log
.
Printf
(
"create ip blacklist watcher error: %v"
,
err
)
return
}
dir
:=
filepath
.
Dir
(
path
)
if
err
:=
watcher
.
Add
(
dir
);
err
!=
nil
{
log
.
Printf
(
"add ip blacklist watch dir error: %v"
,
err
)
watcher
.
Close
()
return
}
go
func
()
{
defer
watcher
.
Close
()
for
{
select
{
case
ev
,
ok
:=
<-
watcher
.
Events
:
if
!
ok
{
return
}
if
ev
.
Name
==
path
{
if
ev
.
Op
&
(
fsnotify
.
Write
|
fsnotify
.
Create
|
fsnotify
.
Rename
)
!=
0
{
exact
,
pats
:=
loadIPBlacklist
(
path
)
ipBlacklistMu
.
Lock
()
ipBlacklist
=
exact
ipBlacklistPatterns
=
pats
ipBlacklistMu
.
Unlock
()
log
.
Printf
(
"ip blacklist reloaded (%d exact, %d patterns) due to event: %s"
,
len
(
exact
),
len
(
pats
),
ev
.
Op
.
String
())
}
if
ev
.
Op
&
fsnotify
.
Remove
!=
0
{
ipBlacklistMu
.
Lock
()
ipBlacklist
=
map
[
string
]
struct
{}{}
ipBlacklistPatterns
=
[]
*
regexp
.
Regexp
{}
ipBlacklistMu
.
Unlock
()
log
.
Printf
(
"ip blacklist file removed, cleared entries"
)
}
}
case
err
,
ok
:=
<-
watcher
.
Errors
:
if
!
ok
{
return
}
log
.
Printf
(
"ip blacklist watcher error: %v"
,
err
)
}
}
}()
}
func
checkRateLimit
(
ip
string
)
bool
{
func
checkRateLimit
(
ip
string
)
bool
{
visitorsMu
.
Lock
()
visitorsMu
.
Lock
()
defer
visitorsMu
.
Unlock
()
defer
visitorsMu
.
Unlock
()
...
@@ -655,7 +803,33 @@ func checkRateLimit(ip string) bool {
...
@@ -655,7 +803,33 @@ func checkRateLimit(ip string) bool {
}
}
v
.
lastSeen
=
time
.
Now
()
v
.
lastSeen
=
time
.
Now
()
// Check if penalty expired
// Check if IP is in static blacklist
if
isIPBlacklisted
(
ip
)
{
if
!
v
.
isStaticBlacklisted
{
// First time detected as blacklisted, force penalty mode
v
.
isStaticBlacklisted
=
true
v
.
limiter
.
SetLimit
(
penaltyLimit
)
v
.
limiter
.
SetBurst
(
penaltyBurst
)
// Set a long punishment duration to avoid frequent checks, or just rely on the flag
v
.
punishedUntil
=
time
.
Now
()
.
Add
(
24
*
time
.
Hour
)
log
.
Printf
(
"IP %s is in static blacklist, enforcing penalty mode"
,
ip
)
}
else
{
// Refresh punishment duration to keep it in penalty mode
if
time
.
Now
()
.
After
(
v
.
punishedUntil
)
{
v
.
punishedUntil
=
time
.
Now
()
.
Add
(
24
*
time
.
Hour
)
}
}
return
v
.
limiter
.
Allow
()
}
else
if
v
.
isStaticBlacklisted
{
// Was blacklisted but now removed from static blacklist
v
.
isStaticBlacklisted
=
false
v
.
punishedUntil
=
time
.
Time
{}
// Clear punishment
v
.
limiter
.
SetLimit
(
normalLimit
)
v
.
limiter
.
SetBurst
(
normalBurst
)
log
.
Printf
(
"IP %s removed from static blacklist, restoring normal limits"
,
ip
)
}
// Check if penalty expired (for dynamic penalty)
if
!
v
.
punishedUntil
.
IsZero
()
{
if
!
v
.
punishedUntil
.
IsZero
()
{
if
time
.
Now
()
.
After
(
v
.
punishedUntil
)
{
if
time
.
Now
()
.
After
(
v
.
punishedUntil
)
{
// Penalty expired, restore normal settings
// Penalty expired, restore normal settings
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment