- | [[
+ | [[
SizeFormatter.sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] |
-
+ |
|
-
+ |
@@ -239,11 +241,11 @@
:percent="statsProgress(record, client.email)" />
- |
+ |
|
-
+ |
[[ client._totalGB + "GB" ]]
∞
|
@@ -256,9 +258,9 @@
- | [[
+ | [[
IntlUtil.formatRelativeTime(client.expiryTime) ]] |
-
+ |
{{ i18n "pages.client.delayedStart" }}
@@ -268,7 +270,7 @@
:percent="expireProgress(client.expiryTime, client.reset)" />
|
- [[ client.reset + "d" ]] |
+ [[ client.reset + "d" ]] |
diff --git a/web/html/form/protocol/vless.html b/web/html/form/protocol/vless.html
index f8ee1542..21abc7b2 100644
--- a/web/html/form/protocol/vless.html
+++ b/web/html/form/protocol/vless.html
@@ -21,16 +21,6 @@
-
-
- None
- X25519 (not
- Post-Quantum)
- ML-KEM-768
- (Post-Quantum)
-
-
@@ -38,16 +28,20 @@
-
- Get New
- keys
+
+
+ X25519
+
+
+ ML-KEM-768
+
Clear
-
+
@@ -81,30 +75,38 @@
-
+
+
+ Vision Seed
+
+
+
+
updateTestseed(0, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900"
+ :value="(inbound.settings.testseed && inbound.settings.testseed[0] !== undefined) ? inbound.settings.testseed[0] : null"
+ @change="(val) => updateTestseed(0, val)" :min="1" :max="9999" :style="{ width: '100%' }" placeholder="900"
addon-before="[0]">
updateTestseed(1, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="500"
+ :value="(inbound.settings.testseed && inbound.settings.testseed[1] !== undefined) ? inbound.settings.testseed[1] : null"
+ @change="(val) => updateTestseed(1, val)" :min="1" :max="9999" :style="{ width: '100%' }" placeholder="500"
addon-before="[1]">
updateTestseed(2, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900"
+ :value="(inbound.settings.testseed && inbound.settings.testseed[2] !== undefined) ? inbound.settings.testseed[2] : null"
+ @change="(val) => updateTestseed(2, val)" :min="1" :max="9999" :style="{ width: '100%' }" placeholder="900"
addon-before="[2]">
updateTestseed(3, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="256"
+ :value="(inbound.settings.testseed && inbound.settings.testseed[3] !== undefined) ? inbound.settings.testseed[3] : null"
+ @change="(val) => updateTestseed(3, val)" :min="1" :max="9999" :style="{ width: '100%' }" placeholder="256"
addon-before="[3]">
@@ -116,6 +118,10 @@
Reset
+
+ Optional. Controls XTLS Vision padding behavior (used only for xtls-rprx-vision).
+ Provide exactly four positive integers to customize padding; otherwise leave empty to use safe defaults.
+
diff --git a/web/html/inbounds.html b/web/html/inbounds.html
index 9559b675..92c5d61b 100644
--- a/web/html/inbounds.html
+++ b/web/html/inbounds.html
@@ -182,6 +182,7 @@
{{ i18n "none" }}
{{ i18n "subscription.active"
@@ -2331,6 +2332,31 @@
#content-layout>.ant-layout-content>.ant-spin-nested-loading>div>.ant-spin {
left: 50vw !important;
}
+
+ /* Keep filter choices in a single horizontal line on phones. */
+ .inbounds-page .mobile-filter-group {
+ display: flex;
+ flex-wrap: nowrap;
+ max-width: 100%;
+ overflow-x: auto;
+ overflow-y: hidden;
+ padding-bottom: 2px;
+ -webkit-overflow-scrolling: touch;
+ scrollbar-width: thin;
+ }
+ .inbounds-page .mobile-filter-group .ant-radio-button-wrapper {
+ flex: 0 0 auto;
+ white-space: nowrap;
+ }
+
+ /* Prevent mobile row content from splitting across multiple lines. */
+ .inbounds-page .ant-table-tbody > tr > td {
+ white-space: nowrap;
+ }
+ .inbounds-page .ant-table-tbody > tr > td:nth-child(3) {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
}
/* Protocol cell — wrap tags into a flex grid with consistent gap so
diff --git a/web/html/modals/inbound_info_modal.html b/web/html/modals/inbound_info_modal.html
index 8ecb59bf..cc2b734d 100644
--- a/web/html/modals/inbound_info_modal.html
+++ b/web/html/modals/inbound_info_modal.html
@@ -90,14 +90,9 @@
[[
inbound.stream.security ]]
- Authentication |
- [[
- inbound.settings.selectedAuth ? inbound.settings.selectedAuth : ''
- ]]
- {{ i18n "none" }}
-
{{ i18n "encryption" }}
- [[
+ [[
inbound.settings.encryption ? inbound.settings.encryption : ''
]]
diff --git a/web/html/modals/inbound_modal.html b/web/html/modals/inbound_modal.html
index ca896096..df2588db 100644
--- a/web/html/modals/inbound_modal.html
+++ b/web/html/modals/inbound_modal.html
@@ -16,6 +16,16 @@
inbound: new Inbound(),
dbInbound: new DBInbound(),
ok() {
+ // Block submit when Vision Seed is XRV-gated and partially/invalidly filled.
+ const seedErr = inModal.testseedError();
+ if (seedErr) {
+ if (typeof Vue !== "undefined" && Vue.prototype && Vue.prototype.$message) {
+ Vue.prototype.$message.error(seedErr);
+ } else {
+ alert(seedErr);
+ }
+ return;
+ }
ObjectUtil.execute(inModal.confirm, inModal.inbound, inModal.dbInbound);
},
show({
@@ -33,16 +43,12 @@
} else {
this.inbound = new Inbound();
}
- // Always ensure testseed is initialized for VLESS protocol (even if vision flow is not set yet)
- // This ensures Vue reactivity works properly
+ // Ensure VLESS settings has a testseed array reference for Vue reactivity,
+ // but leave it empty so we don't auto-emit defaults — user must explicitly
+ // fill all four fields, or leave blank to fall back to backend defaults.
if (this.inbound.protocol === Protocols.VLESS && this.inbound.settings) {
- if (
- !this.inbound.settings.testseed ||
- !Array.isArray(this.inbound.settings.testseed) ||
- this.inbound.settings.testseed.length < 4
- ) {
- // Create a new array to ensure Vue reactivity
- this.inbound.settings.testseed = [900, 500, 900, 256].slice();
+ if (!Array.isArray(this.inbound.settings.testseed)) {
+ this.inbound.settings.testseed = [];
}
}
if (dbInbound) {
@@ -61,48 +67,50 @@
loading(loading = true) {
inModal.confirmLoading = loading;
},
- // Vision Seed methods - always available regardless of Vue context
+ // Returns an error string when the current testseed state would be rejected,
+ // or "" when it's valid (empty == use defaults; full 4 positive ints == custom).
+ testseedError() {
+ if (!inModal.inbound || inModal.inbound.protocol !== Protocols.VLESS) return "";
+ if (typeof inModal.inbound.canEnableVisionSeed === "function"
+ && !inModal.inbound.canEnableVisionSeed()) return "";
+ const seed = inModal.inbound.settings && inModal.inbound.settings.testseed;
+ if (!Array.isArray(seed) || seed.length === 0) return "";
+ const filled = seed.filter(v => v !== null && v !== undefined && v !== "");
+ if (filled.length === 0) return "";
+ if (seed.length !== 4 || filled.length !== 4 ||
+ !seed.every(v => Number.isInteger(v) && v > 0)) {
+ return "Provide exactly 4 positive integers or leave empty to use defaults.";
+ }
+ return "";
+ },
+ // Vision Seed helpers — always available regardless of Vue context
updateTestseed(index, value) {
- // Use inModal.inbound explicitly to ensure correct context
if (!inModal.inbound || !inModal.inbound.settings) return;
- // Ensure testseed is initialized
- if (
- !inModal.inbound.settings.testseed ||
- !Array.isArray(inModal.inbound.settings.testseed)
- ) {
- inModal.inbound.settings.testseed = [900, 500, 900, 256];
+ if (!Array.isArray(inModal.inbound.settings.testseed)) {
+ inModal.inbound.settings.testseed = [];
}
- // Ensure array has enough elements
- while (inModal.inbound.settings.testseed.length <= index) {
- inModal.inbound.settings.testseed.push(0);
+ const seed = inModal.inbound.settings.testseed;
+ while (seed.length <= index) seed.push(null);
+ seed[index] = value;
+ // If user cleared every slot, collapse back to empty so we omit testseed entirely.
+ if (seed.every(v => v === null || v === undefined || v === "")) {
+ inModal.inbound.settings.testseed = [];
}
- // Update value
- inModal.inbound.settings.testseed[index] = value;
},
setRandomTestseed() {
- // Use inModal.inbound explicitly to ensure correct context
if (!inModal.inbound || !inModal.inbound.settings) return;
- // Ensure testseed is initialized
- if (
- !inModal.inbound.settings.testseed ||
- !Array.isArray(inModal.inbound.settings.testseed) ||
- inModal.inbound.settings.testseed.length < 4
- ) {
- inModal.inbound.settings.testseed = [900, 500, 900, 256].slice();
- }
- // Create new array with random values
+ // Positive integers only (>=1) so the array passes validation and gets emitted.
inModal.inbound.settings.testseed = [
- Math.floor(Math.random() * 1000),
- Math.floor(Math.random() * 1000),
- Math.floor(Math.random() * 1000),
- Math.floor(Math.random() * 1000),
+ Math.floor(Math.random() * 999) + 1,
+ Math.floor(Math.random() * 999) + 1,
+ Math.floor(Math.random() * 999) + 1,
+ Math.floor(Math.random() * 999) + 1,
];
},
resetTestseed() {
- // Use inModal.inbound explicitly to ensure correct context
if (!inModal.inbound || !inModal.inbound.settings) return;
- // Reset testseed to default values
- inModal.inbound.settings.testseed = [900, 500, 900, 256].slice();
+ // Empty == "use server defaults [900, 500, 900, 256]"; placeholders show in the form.
+ inModal.inbound.settings.testseed = [];
},
});
@@ -170,27 +178,17 @@
});
}
},
- // Ensure testseed is always initialized when vision flow is enabled
+ // Keep testseed as a valid array reference for Vue reactivity while the user
+ // toggles flows — but do NOT auto-fill defaults. Empty means "use server defaults"
+ // and is the only way the form omits testseed from the outbound JSON.
"inModal.inbound.settings.vlesses": {
handler() {
if (
inModal.inbound.protocol === Protocols.VLESS &&
inModal.inbound.settings &&
- inModal.inbound.settings.vlesses
+ !Array.isArray(inModal.inbound.settings.testseed)
) {
- const hasVisionFlow = inModal.inbound.settings.vlesses.some(
- (c) =>
- c.flow === "xtls-rprx-vision" ||
- c.flow === "xtls-rprx-vision-udp443",
- );
- if (
- hasVisionFlow &&
- (!inModal.inbound.settings.testseed ||
- !Array.isArray(inModal.inbound.settings.testseed) ||
- inModal.inbound.settings.testseed.length < 4)
- ) {
- inModal.inbound.settings.testseed = [900, 500, 900, 256];
- }
+ inModal.inbound.settings.testseed = [];
}
},
deep: true,
@@ -304,12 +302,11 @@
this.inbound.stream.tls.echServerKeys = "";
this.inbound.stream.tls.settings.echConfigList = "";
},
- async getNewVlessEnc() {
- const selected = inModal.inbound.settings.selectedAuth;
- if (!selected) {
- this.clearVlessEnc();
- return;
- }
+ // Pulls the requested auth block from `xray vlessenc` (which always returns
+ // both X25519 and ML-KEM-768 variants) and applies it to the inbound's
+ // decryption/encryption strings. The auth mode is implied by the resulting
+ async getNewVlessEnc(authLabel) {
+ if (!authLabel) return;
inModal.loading(true);
const msg = await HttpUtil.get("/panel/api/server/getNewVlessEnc");
@@ -320,10 +317,10 @@
}
const auths = msg.obj.auths || [];
- const block = auths.find((a) => a.label === selected);
+ const block = auths.find((a) => a.label === authLabel);
if (!block) {
- console.error("No auth block for", selected);
+ console.error("No auth block for", authLabel);
return;
}
@@ -333,37 +330,37 @@
clearVlessEnc() {
this.inbound.settings.decryption = "none";
this.inbound.settings.encryption = "none";
- this.inbound.settings.selectedAuth = undefined;
},
- // Vision Seed methods - must be in Vue methods for proper binding
+ // Vision Seed methods - must be in Vue methods for proper template binding.
+ // Mirror the inModal helpers but use Vue.set so the form re-renders.
updateTestseed(index, value) {
- // Ensure testseed is initialized
- if (
- !this.inbound.settings.testseed ||
- !Array.isArray(this.inbound.settings.testseed)
- ) {
- this.$set(this.inbound.settings, "testseed", [900, 500, 900, 256]);
+ if (!Array.isArray(this.inbound.settings.testseed)) {
+ this.$set(this.inbound.settings, "testseed", []);
}
- // Ensure array has enough elements
- while (this.inbound.settings.testseed.length <= index) {
- this.inbound.settings.testseed.push(0);
+ const seed = this.inbound.settings.testseed;
+ while (seed.length <= index) seed.push(null);
+ this.$set(seed, index, value);
+ // Collapse to empty when every slot is cleared so testseed is omitted from JSON.
+ if (seed.every(v => v === null || v === undefined || v === "")) {
+ this.$set(this.inbound.settings, "testseed", []);
}
- // Update value using Vue.set for reactivity
- this.$set(this.inbound.settings.testseed, index, value);
},
setRandomTestseed() {
- // Create new array with random values and use Vue.set for reactivity
+ // Positive integers only (>=1) so the resulting array passes validation.
const newSeed = [
- Math.floor(Math.random() * 1000),
- Math.floor(Math.random() * 1000),
- Math.floor(Math.random() * 1000),
- Math.floor(Math.random() * 1000),
+ Math.floor(Math.random() * 999) + 1,
+ Math.floor(Math.random() * 999) + 1,
+ Math.floor(Math.random() * 999) + 1,
+ Math.floor(Math.random() * 999) + 1,
];
this.$set(this.inbound.settings, "testseed", newSeed);
},
resetTestseed() {
- // Reset testseed to default values using Vue.set for reactivity
- this.$set(this.inbound.settings, "testseed", [900, 500, 900, 256]);
+ // Empty == "use server defaults [900, 500, 900, 256]". Placeholders will show in the form.
+ this.$set(this.inbound.settings, "testseed", []);
+ },
+ testseedError() {
+ return inModal.testseedError();
},
},
});
diff --git a/web/html/settings/xray/outbounds.html b/web/html/settings/xray/outbounds.html
index 6a5dd40c..83c25df4 100644
--- a/web/html/settings/xray/outbounds.html
+++ b/web/html/settings/xray/outbounds.html
@@ -4,8 +4,7 @@
- {{ i18n
- "pages.xray.outbound.addOutbound" }}
+ {{ i18n "pages.xray.outbound.addOutbound" }}
WARP
NordVPN
@@ -25,9 +24,114 @@
-
+
+ —
+
+
+
+
+
{{ template "page/body_end" .}}
\ No newline at end of file
diff --git a/web/job/check_client_ip_job.go b/web/job/check_client_ip_job.go
index 7f0ac2cf..e16cced2 100644
--- a/web/job/check_client_ip_job.go
+++ b/web/job/check_client_ip_job.go
@@ -403,16 +403,6 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
shouldCleanLog := false
j.disAllowedIps = []string{}
- // Open log file
- logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
- if err != nil {
- logger.Errorf("failed to open IP limit log file: %s", err)
- return false
- }
- defer logIpFile.Close()
- log.SetOutput(logIpFile)
- log.SetFlags(log.LstdFlags)
-
// historical db-only ips are excluded from this count on purpose.
var keptLive []IPWithTimestamp
if len(liveIps) > limitIp {
@@ -422,13 +412,25 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
keptLive = liveIps[:limitIp]
bannedLive := liveIps[limitIp:]
+ // Open log file only when a ban entry needs to be written.
+ // Use a local logger to avoid mutating the global log.* state,
+ // which would redirect all standard-library logging to this file
+ // and leave a dangling closed-file handle after the defer fires.
+ logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
+ if err != nil {
+ logger.Errorf("failed to open IP limit log file: %s", err)
+ return false
+ }
+ defer logIpFile.Close()
+ ipLogger := log.New(logIpFile, "", log.LstdFlags)
+
// log format is load-bearing: x-ui.sh create_iplimit_jails builds
// filter.d/3x-ipl.conf with
// failregex = \[LIMIT_IP\]\s*Email\s*=\s*.+\s*\|\|\s*Disconnecting OLD IP\s*=\s*\s*\|\|\s*Timestamp\s*=\s*\d+
// don't change the wording.
for _, ipTime := range bannedLive {
j.disAllowedIps = append(j.disAllowedIps, ipTime.IP)
- log.Printf("[LIMIT_IP] Email = %s || Disconnecting OLD IP = %s || Timestamp = %d", clientEmail, ipTime.IP, ipTime.Timestamp)
+ ipLogger.Printf("[LIMIT_IP] Email = %s || Disconnecting OLD IP = %s || Timestamp = %d", clientEmail, ipTime.IP, ipTime.Timestamp)
}
// force xray to drop existing connections from banned ips
diff --git a/web/service/inbound.go b/web/service/inbound.go
index 1155b916..499dc8f6 100644
--- a/web/service/inbound.go
+++ b/web/service/inbound.go
@@ -1213,6 +1213,28 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
settingsClients[clientIndex] = interfaceClients[0]
oldSettings["clients"] = settingsClients
+ // testseed is only meaningful when at least one VLESS client uses the exact
+ // xtls-rprx-vision flow. The client-edit path only rewrites a single client,
+ // so re-check the flow set here and strip a stale testseed when nothing in the
+ // inbound still warrants it. The full-inbound update path already handles this
+ // on the JS side via VLESSSettings.toJson().
+ if oldInbound.Protocol == model.VLESS {
+ hasVisionFlow := false
+ for _, c := range settingsClients {
+ cm, ok := c.(map[string]any)
+ if !ok {
+ continue
+ }
+ if flow, _ := cm["flow"].(string); flow == "xtls-rprx-vision" {
+ hasVisionFlow = true
+ break
+ }
+ }
+ if !hasVisionFlow {
+ delete(oldSettings, "testseed")
+ }
+ }
+
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
if err != nil {
return false, err
@@ -2897,6 +2919,7 @@ func (s *InboundService) MigrationRequirements() {
if ok {
// Fix Client configuration problems
var newClients []any
+ hasVisionFlow := false
for client_index := range clients {
c := clients[client_index].(map[string]any)
@@ -2922,6 +2945,9 @@ func (s *InboundService) MigrationRequirements() {
c["flow"] = ""
}
}
+ if flow, _ := c["flow"].(string); flow == "xtls-rprx-vision" {
+ hasVisionFlow = true
+ }
// Backfill created_at and updated_at
if _, ok := c["created_at"]; !ok {
c["created_at"] = time.Now().Unix() * 1000
@@ -2930,6 +2956,15 @@ func (s *InboundService) MigrationRequirements() {
newClients = append(newClients, any(c))
}
settings["clients"] = newClients
+
+ // Drop orphaned testseed: VLESS-only field, only meaningful when at least
+ // one client uses the exact xtls-rprx-vision flow. Older versions saved it
+ // for any non-empty flow (including the UDP variant) or kept it after the
+ // flow was cleared from the client modal — clean those up here.
+ if inbounds[inbound_index].Protocol == model.VLESS && !hasVisionFlow {
+ delete(settings, "testseed")
+ }
+
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
if err != nil {
return
diff --git a/web/service/server.go b/web/service/server.go
index 0d531e12..3bb93028 100644
--- a/web/service/server.go
+++ b/web/service/server.go
@@ -315,13 +315,21 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
}
// Network stats
- ioStats, err := net.IOCounters(false)
+ ioStats, err := net.IOCounters(true)
if err != nil {
logger.Warning("get io counters failed:", err)
- } else if len(ioStats) > 0 {
- ioStat := ioStats[0]
- status.NetTraffic.Sent = ioStat.BytesSent
- status.NetTraffic.Recv = ioStat.BytesRecv
+ } else {
+ var totalSent, totalRecv uint64
+ for _, iface := range ioStats {
+ name := strings.ToLower(iface.Name)
+ if isVirtualInterface(name) {
+ continue
+ }
+ totalSent += iface.BytesSent
+ totalRecv += iface.BytesRecv
+ }
+ status.NetTraffic.Sent = totalSent
+ status.NetTraffic.Recv = totalRecv
if lastStatus != nil {
duration := now.Sub(lastStatus.T)
@@ -331,8 +339,6 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
status.NetIO.Up = up
status.NetIO.Down = down
}
- } else {
- logger.Warning("can not find io counters")
}
// TCP/UDP connections
@@ -860,6 +866,34 @@ func (s *ServerService) GetXrayLogs(
return entries
}
+// isVirtualInterface returns true for loopback and virtual/tunnel interfaces
+// that should be excluded from network traffic statistics.
+func isVirtualInterface(name string) bool {
+ // Exact matches
+ if name == "lo" || name == "lo0" {
+ return true
+ }
+ // Prefix matches for virtual/tunnel interfaces
+ virtualPrefixes := []string{
+ "loopback",
+ "docker",
+ "br-",
+ "veth",
+ "virbr",
+ "tun",
+ "tap",
+ "wg",
+ "tailscale",
+ "zt",
+ }
+ for _, prefix := range virtualPrefixes {
+ if strings.HasPrefix(name, prefix) {
+ return true
+ }
+ }
+ return false
+}
+
func logEntryContains(line string, suffixes []string) bool {
for _, sfx := range suffixes {
if strings.Contains(line, sfx+"]") {
diff --git a/x-ui.sh b/x-ui.sh
index 73e99195..31ecc683 100644
--- a/x-ui.sh
+++ b/x-ui.sh
@@ -2034,14 +2034,14 @@ backend=auto
filter=3x-ipl
action=3x-ipl
logpath=${iplimit_log_path}
-maxretry=2
+maxretry=1
findtime=32
bantime=${bantime}m
EOF
cat << EOF > /etc/fail2ban/filter.d/3x-ipl.conf
[Definition]
-datepattern = ^%%Y/%%m/%%d %%H:%%M:%%S
+datepattern = ^%Y/%m/%d %H:%M:%S
failregex = \[LIMIT_IP\]\s*Email\s*=\s*.+\s*\|\|\s*Disconnecting OLD IP\s*=\s*\s*\|\|\s*Timestamp\s*=\s*\d+
ignoreregex =
EOF
@@ -2062,10 +2062,10 @@ actionstop = -D -p -j f2b-
actioncheck = -n -L | grep -q 'f2b-[ \t]'
actionban = -I f2b- 1 -s -j
- echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") BAN [Email] = [IP] = banned for seconds." >> ${iplimit_banned_log_path}
+ echo "\$(date +"%Y/%m/%d %H:%M:%S") BAN [Email] = [IP] = banned for seconds." >> ${iplimit_banned_log_path}
actionunban = -D f2b- -s -j
- echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = [IP] = unbanned." >> ${iplimit_banned_log_path}
+ echo "\$(date +"%Y/%m/%d %H:%M:%S") UNBAN [Email] = [IP] = unbanned." >> ${iplimit_banned_log_path}
[Init]
name = default
| |