shadow1ng/fscan · warning
network_rate_limited
Error message
network_rate_limited
What it means
ScanSession.DialTCP checks CanSendPacketWith before every outbound TCP dial. If the session's rate/packet limits are exhausted, the dial is refused and this error wraps the limit reason. It prevents the scan from exceeding configured network budgets.
Source
Thrown at common/session.go:97
func (s *ScanSession) LogVuln(result string) {
if s.loggingEnabled() {
LogVuln(result)
}
}
// LogError writes through the session's logging policy.
func (s *ScanSession) LogError(errMsg string) {
if s.loggingEnabled() {
LogError(errMsg)
}
}
// DialTCP 创建 TCP 连接,内含限速检查、代理、计数
func (s *ScanSession) DialTCP(ctx context.Context, network, address string, timeout time.Duration) (net.Conn, error) {
// 检查发包限制
if ok, err := CanSendPacketWith(s.Config, s.State); !ok {
s.LogError(i18n.Tr("tcp_connection_restricted", address, err.Error()))
return nil, fmt.Errorf("%s", i18n.Tr("network_rate_limited", err.Error()))
}
// 获取 dialer
dialer, err := s.getDialer(timeout)
if err != nil {
s.LogError(i18n.Tr("proxy_dialer_failed", err))
s.State.IncrementTCPFailedPacketCount()
return nil, err
}
conn, err := dialer.DialContext(ctx, network, address)
if err != nil {
s.State.IncrementTCPFailedPacketCount()
s.LogDebug(i18n.Tr("connection_failed", address, err))
return nil, err
}
// SO_LINGER=0: 连接关闭时立即发送 RST,避免 TIME_WAIT 堆积View on GitHub (pinned to 95cc12e753)
Solutions
- Wait/back off until the rate window resets, or reduce scan concurrency so sends stay under the limit.
- Raise the packet/rate limits in the ScanSession Config if the budget is genuinely too small.
- Check the wrapped reason in err.Error() to distinguish a temporary rate window from a hard cap, and handle accordingly.
Example fix
// before
conn, err := session.DialTCP(ctx, "tcp", addr, timeout) // fails with network_rate_limited
// after
if ok, err := common.CanSendPacketWith(session.Config, session.State); ok {
conn, err = session.DialTCP(ctx, "tcp", addr, timeout)
} else {
time.Sleep(backoff) // wait for rate window to reset, then retry
} Defensive patterns
Strategy: retry
Validate before calling
ok, limitErr := common.CanSendPacketWith(session.Config, session.State)
if !ok { /* defer or back off before dialing */ } Try / catch
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
if err != nil {
var backoff = initialBackoff
for retries := 0; retries < maxRetries && err != nil; retries++ {
select {
case <-time.After(backoff):
case <-ctx.Done():
return ctx.Err()
}
conn, err = session.DialTCP(ctx, "tcp", addr, timeout)
backoff *= 2
}
} Prevention
- Check CanSendPacketWith before burst dialing
- Tune Config packet/rate limits to target scope and bandwidth
- Cap scan concurrency so the shared session budget is respected
When it happens
Trigger: Any code path that dials TCP through the session (connectWithRetry, reconnectIfNeeded, service identification, port reachability checks) after the configured send-rate or packet count limit has been hit.
Common situations: Long-running scans hitting the configured packets-per-second/total caps; misconfigured ScanSession.Config limits set too low for the target scope; concurrent goroutines consuming the budget faster than expected.
Related errors
- [dial err] %v
- read %s
- %s: %w [connection_failed_plain]
- %s: %w [listen_port_failed]
- socks5_target_connect_failed: %w
AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06).
Data as JSON: /api/errors/1d8fec2642642e15.
Report an issue: GitHub.