Skip to content

Adding predictive scanning#1666

Open
Mzack9999 wants to merge 5 commits intodevfrom
1665-predictive-port-scanning
Open

Adding predictive scanning#1666
Mzack9999 wants to merge 5 commits intodevfrom
1665-predictive-port-scanning

Conversation

@Mzack9999
Copy link
Copy Markdown
Member

@Mzack9999 Mzack9999 commented Mar 27, 2026

Description

Adds a smart scan mode that reorders user-provided ports based on a port correlation model, prioritizing ports most likely to be open. When a port is found open, correlated ports get dynamically boosted in the scan queue. This doesn't add or remove any ports from the user's list, it only changes the order they're scanned in. In local benchmarks against a /24 subnet with 1000 ports, smart scan achieved full coverage parity with the standard scan while completing ~95x faster by finding open ports early and deprioritizing unlikely candidates.

# standard scan
root@box:~# time naabu -host 192.168.5.0/24 -s s -top-ports 100 -rate 10000 -retries 3 -silent | wc -l
89
real	18m44.473s
user	0m11.407s
sys	0m22.912s

# smart scan (-ss)
root@box:~# time naabu -host 192.168.5.0/24 -s s -top-ports 100 -rate 10000 -retries 3 -silent -ss | wc -l
89
real	0m11.820s
user	0m0.437s
sys	0m1.527s

Summary by CodeRabbit

  • New Features

    • Predictive "smart scan" mode that prioritizes ports using a learned port-correlation model
    • Flags to enable smart scan and set prediction confidence threshold (0–100%, default 20)
    • Faster raw TCP SYN sender and optimized IPv4 target indexing for high-speed IPv4 scanning
    • New queries to list discovered open ports and count hosts per port
  • Bug Fixes

    • Improved transport packet handling to validate transport-layer presence before processing
  • Documentation

    • README updated with new optimization flags and alignment fixes

@Mzack9999 Mzack9999 self-assigned this Mar 27, 2026
@Mzack9999 Mzack9999 marked this pull request as draft March 27, 2026 17:44
@neo-by-projectdiscovery-dev
Copy link
Copy Markdown

neo-by-projectdiscovery-dev bot commented Mar 27, 2026

Neo - PR Security Review

No security issues found

Comment @pdneo help for available commands. · Open in Neo

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

Walkthrough

Adds a predictive "smart scan" mode: a thread-safe port correlation model, priority-queue–driven scan ordering, fast raw TCP SYN sender and IPv4 indexing, CLI flags to enable/configure predictions, and supporting result/query utilities and validation.

Changes

Cohort / File(s) Summary
Prediction Model
pkg/prediction/model.go, pkg/prediction/default_model.go, pkg/prediction/model_test.go
New concurrency-safe port correlation Model with Train/Merge/Predict/Prioritize APIs, default hard-coded correlations, and comprehensive tests validating training, inference, and merging.
Smart Scan Workflow
pkg/runner/smartscan.go, pkg/runner/runner.go
New predictive scanning flow driven by a priority heap of ports; Runner dispatches to runPredictiveScan when enabled and iteratively boosts correlated ports based on discoveries. Integrates with existing scan enqueue/raw paths and provides per-port scanning helpers.
Fast Raw Sender & Indexing
pkg/runner/fastsend.go, pkg/runner/fastsend_test.go
Introduces SYNSender for high-performance raw IPv4 SYN sending with incremental checksum updates, fast IPv4 parse/format utilities, and a CIDR-based targetIndex; tests and benchmarks added.
CLI Options & Validation
pkg/runner/options.go, pkg/runner/validate.go, README.md
Adds --smart-scan/-ss and --prediction-threshold/-pt (default 20) to Options, enforces mutual exclusion with Stream/Passive modes and threshold ∈ [0,100]; README updated.
Result Utilities
pkg/result/results.go
Added thread-safe aggregate queries: GetOpenPortNumbers() and GetHostCountForPort(portNum int).
Transport Handling Fix
pkg/scan/scan_raw.go
Refined TransportReadWorker to pre-check for transport layer presence and conditionally extract IPv4/IPv6 before invoking the transport callback.
Small Tests / Docs
pkg/scan/scan_type_test.go, go.mod, README.md
Minor test comment formatting tweak and gopacket version bump in go.mod. README spacing/alignment adjusted for optimization flags.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant Runner as Runner
    participant Model as Prediction Model
    participant Queue as Port Queue
    participant Scanner as Scanner
    participant Sender as SYN Sender
    participant Network as Network

    User->>Runner: Start enumeration (SmartScan=true)
    Runner->>Model: Load DefaultModel()
    Model-->>Runner: Correlations ready

    Runner->>Queue: Build initial heap from ports
    Queue-->>Runner: Heap initialized

    loop per retry
        Runner->>Scanner: collectOpenPorts()
        Scanner-->>Runner: []openPorts
        Runner->>Queue: rebuild/prioritize using correlations
        Queue-->>Runner: prioritized ports

        loop while queue not empty
            Runner->>Queue: pop() next port
            Queue-->>Runner: port
            Runner->>Sender: send probes for port (raw or enqueue)
            Sender->>Network: emit SYNs
            Network-->>Scanner: responses
            Scanner-->>Runner: OnReceive(open port)
            Runner->>Queue: boostCorrelated(newOpenPort)
            Queue-->>Queue: heap.Fix() (priority update)
        end
    end

    Runner-->>User: Scan complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I nibbled data, hopped in queues,

Correlated ports and changed my shoes.
SYNs I toss with careful art,
Predictions point the eager part,
Hooray — smart scanning's where we start!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title "Adding predictive scanning" directly and concisely describes the main feature being introduced—a predictive/smart scanning capability that prioritizes ports using a correlation model.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 1665-predictive-port-scanning

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (6)
pkg/runner/runner.go (1)

554-681: LGTM!

The existing retry-based scanning logic is correctly preserved in the else branch. The reorganization cleanly separates the two scanning strategies while maintaining backward compatibility for the standard scan flow.

One observation: progress bar initialization is skipped for smart scan mode. Consider adding progress indication to runPredictiveScan in a future iteration for user feedback during long scans.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/runner/runner.go` around lines 554 - 681, The predictive scan branch
(runPredictiveScan) lacks the progress bar/statistics initialization present in
the standard scan loop; update runPredictiveScan to initialize r.stats when
r.options.EnableProgressBar is true by computing the same Range from
targetsCount*portsCount, calling r.stats.AddStatic for
"ports","hosts","retries","startedAt" and r.stats.AddCounter for
"packets","errors","total" (using
Range*uint64(r.options.Retries)+targetsWithPortCount) and "hosts_with_port",
then call r.stats.Start() and handle the returned error similarly to the current
loop; reference runPredictiveScan, r.options.EnableProgressBar,
r.stats.AddStatic, r.stats.AddCounter, r.stats.Start, targetsCount, portsCount,
targetsWithPortCount and Range to locate and mirror the initialization logic.
pkg/runner/smartscan.go (2)

412-432: Silent error handling in buildPopularityRank for malformed port ranges.

strconv.Atoi errors are silently discarded (lines 419-420, 426). If NmapTop1000 contains malformed data, ports would be assigned rank 0 or the rank map would have incorrect entries.

Consider logging a warning for malformed entries during development/debugging:

 		if strings.Contains(segment, "-") {
 			parts := strings.Split(segment, "-")
-			start, _ := strconv.Atoi(parts[0])
-			end, _ := strconv.Atoi(parts[1])
+			start, err1 := strconv.Atoi(parts[0])
+			end, err2 := strconv.Atoi(parts[1])
+			if err1 != nil || err2 != nil {
+				continue // skip malformed range
+			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/runner/smartscan.go` around lines 412 - 432, The buildPopularityRank
function silently ignores strconv.Atoi errors when parsing NmapTop1000 segments
which can produce incorrect rank entries; update buildPopularityRank to validate
Atoi errors for both single ports and range boundaries (using the existing
NmapTop1000 string parsing), skip malformed segments instead of inserting
zero/invalid entries into the rank map, and emit a concise warning/log (using
the package logger or log.Printf) identifying the bad segment and reason so
malformed inputs are detectable during debugging while preserving normal
behavior for valid ports.

183-200: Consider extracting common skip-check logic to reduce duplication.

The skip checks (excluded IPs, skipped hosts, port threshold, CDN, already-has-port) are duplicated between the fast path (lines 183-200) and standard path (lines 217-250). While the paths have slightly different IP types ([4]byte + string vs. string only), the logic is nearly identical.

Extracting this into a helper that accepts the IP string could reduce maintenance burden.

Also applies to: 217-250

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/runner/smartscan.go` around lines 183 - 200, Extract the repeated
skip-check sequence into a single helper (e.g., shouldSkip(ip string, p int)
bool) and call it from both the fast and standard paths; move logic using
r.excludedIpsNP.ValidateAddress, r.scanner.ScanResults.HasSkipped,
r.options.PortThreshold with r.scanner.ScanResults.GetPortCount and
r.scanner.IPRanger.GetHostsByIP (and r.scanner.ScanResults.AddSkipped),
r.scanner.ScanResults.IPHasPort, and r.canIScanIfCDN into that helper so the
checks and gologger.Info message are centralized; in the fast path convert the
[4]byte+string IP to a string before calling shouldSkip and preserve existing
behavior/short-circuits (return true to indicate skip, false to proceed).
pkg/runner/fastsend.go (2)

46-48: Discarding the routing error may hide configuration problems.

scan.PkgRouter.Route() returns an error when no route is found, but it's silently ignored here. If routing fails, srcIP will be nil and the subsequent check will return errNoSourceIP, but the actual root cause (routing error) is lost.

Proposed fix
 	} else if scan.PkgRouter != nil {
-		_, _, srcIP, _ = scan.PkgRouter.Route(net.IPv4(1, 1, 1, 1))
+		var routeErr error
+		_, _, srcIP, routeErr = scan.PkgRouter.Route(net.IPv4(1, 1, 1, 1))
+		if routeErr != nil {
+			return nil, fmt.Errorf("route lookup failed: %w", routeErr)
+		}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/runner/fastsend.go` around lines 46 - 48, The call to
scan.PkgRouter.Route in fastsend.go discards its error, hiding routing failures;
update the code around the scan.PkgRouter.Route(net.IPv4(1,1,1,1)) invocation
(in the same block that currently assigns _, _, srcIP, _) to capture the
returned error, check if err != nil and propagate or wrap that error instead of
ignoring it (so the caller sees the routing failure rather than only
errNoSourceIP), and only proceed to use srcIP when err == nil; reference
scan.PkgRouter.Route and the surrounding fastsend logic to make this change.

199-217: Potential integer overflow when converting index to uint32.

On line 206, uint32(index) can overflow if index exceeds math.MaxUint32 (4,294,967,295). For a /0 CIDR (entire IPv4 space), the target count is 2^32, so index could reach MaxUint32, which is valid. However, if multiple CIDRs are used and index accumulates across entries, it could exceed this.

In practice, the subtraction at line 214 keeps index within the current entry's count, so this should be safe for valid IPv4 CIDRs. Consider adding a defensive check or documenting this constraint.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/runner/fastsend.go` around lines 199 - 217, The pickIPv4 function can
overflow when casting index to uint32; before computing val := e.baseIPv4 +
uint32(index) add a defensive check that index is within the 32-bit unsigned
range and that the sum won't wrap (e.g. ensure index <= math.MaxUint32 and
uint64(e.baseIPv4)+uint64(index) <= math.MaxUint32), then perform the addition
using a wider type (uint64) and cast safely to uint32; if the check fails,
return the empty result (same behavior as when not v4). This touches pickIPv4
and the variables index, e.count and e.baseIPv4.
pkg/prediction/model.go (1)

82-84: Non-deterministic ordering for ties in Predict results.

Unlike Prioritize (which breaks ties by port number at lines 178-183), Predict uses an unstable sort without a tie-breaker. When multiple predictions have the same confidence, their order is non-deterministic.

Proposed fix to add tie-breaker
 	sort.Slice(predictions, func(i, j int) bool {
+		if predictions[i].Confidence != predictions[j].Confidence {
+			return predictions[i].Confidence > predictions[j].Confidence
+		}
+		return predictions[i].Port < predictions[j].Port
-		return predictions[i].Confidence > predictions[j].Confidence
 	})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/prediction/model.go` around lines 82 - 84, The sort in Predict uses
sort.Slice on the predictions slice without a tie-breaker, causing
non-deterministic ordering for equal Confidence; update the comparator in the
sort.Slice call inside Predict to first compare Confidence (descending) and when
equal compare a stable secondary key (e.g., predictions[i].Port <
predictions[j].Port or another stable field on the prediction struct) so ties
are deterministically ordered (mirror the Prioritize tie-breaker behavior).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/prediction/model.go`:
- Around line 134-150: Model.Merge can deadlock if two goroutines call Merge on
each other; fix by avoiding holding other.mu while acquiring m.mu. In Merge,
acquire other.mu.RLock(), shallow-copy or deep-copy other.correlations into a
local variable, then release other.mu.RUnlock() before acquiring m.mu.Lock();
finally merge from the local copy into m.correlations. Reference: Model.Merge,
Model.mu, other.mu, and the correlations map when locating where to copy and
merge.

In `@pkg/runner/fastsend.go`:
- Line 8: The build fails due to a missing module import
"github.com/Mzack9999/gopacket/rawsend" in pkg/runner/fastsend.go; run "go get
github.com/Mzack9999/gopacket/rawsend" to add the dependency, then commit the
updated go.mod and go.sum so the import in fastsend.go resolves during CI.

---

Nitpick comments:
In `@pkg/prediction/model.go`:
- Around line 82-84: The sort in Predict uses sort.Slice on the predictions
slice without a tie-breaker, causing non-deterministic ordering for equal
Confidence; update the comparator in the sort.Slice call inside Predict to first
compare Confidence (descending) and when equal compare a stable secondary key
(e.g., predictions[i].Port < predictions[j].Port or another stable field on the
prediction struct) so ties are deterministically ordered (mirror the Prioritize
tie-breaker behavior).

In `@pkg/runner/fastsend.go`:
- Around line 46-48: The call to scan.PkgRouter.Route in fastsend.go discards
its error, hiding routing failures; update the code around the
scan.PkgRouter.Route(net.IPv4(1,1,1,1)) invocation (in the same block that
currently assigns _, _, srcIP, _) to capture the returned error, check if err !=
nil and propagate or wrap that error instead of ignoring it (so the caller sees
the routing failure rather than only errNoSourceIP), and only proceed to use
srcIP when err == nil; reference scan.PkgRouter.Route and the surrounding
fastsend logic to make this change.
- Around line 199-217: The pickIPv4 function can overflow when casting index to
uint32; before computing val := e.baseIPv4 + uint32(index) add a defensive check
that index is within the 32-bit unsigned range and that the sum won't wrap (e.g.
ensure index <= math.MaxUint32 and uint64(e.baseIPv4)+uint64(index) <=
math.MaxUint32), then perform the addition using a wider type (uint64) and cast
safely to uint32; if the check fails, return the empty result (same behavior as
when not v4). This touches pickIPv4 and the variables index, e.count and
e.baseIPv4.

In `@pkg/runner/runner.go`:
- Around line 554-681: The predictive scan branch (runPredictiveScan) lacks the
progress bar/statistics initialization present in the standard scan loop; update
runPredictiveScan to initialize r.stats when r.options.EnableProgressBar is true
by computing the same Range from targetsCount*portsCount, calling
r.stats.AddStatic for "ports","hosts","retries","startedAt" and
r.stats.AddCounter for "packets","errors","total" (using
Range*uint64(r.options.Retries)+targetsWithPortCount) and "hosts_with_port",
then call r.stats.Start() and handle the returned error similarly to the current
loop; reference runPredictiveScan, r.options.EnableProgressBar,
r.stats.AddStatic, r.stats.AddCounter, r.stats.Start, targetsCount, portsCount,
targetsWithPortCount and Range to locate and mirror the initialization logic.

In `@pkg/runner/smartscan.go`:
- Around line 412-432: The buildPopularityRank function silently ignores
strconv.Atoi errors when parsing NmapTop1000 segments which can produce
incorrect rank entries; update buildPopularityRank to validate Atoi errors for
both single ports and range boundaries (using the existing NmapTop1000 string
parsing), skip malformed segments instead of inserting zero/invalid entries into
the rank map, and emit a concise warning/log (using the package logger or
log.Printf) identifying the bad segment and reason so malformed inputs are
detectable during debugging while preserving normal behavior for valid ports.
- Around line 183-200: Extract the repeated skip-check sequence into a single
helper (e.g., shouldSkip(ip string, p int) bool) and call it from both the fast
and standard paths; move logic using r.excludedIpsNP.ValidateAddress,
r.scanner.ScanResults.HasSkipped, r.options.PortThreshold with
r.scanner.ScanResults.GetPortCount and r.scanner.IPRanger.GetHostsByIP (and
r.scanner.ScanResults.AddSkipped), r.scanner.ScanResults.IPHasPort, and
r.canIScanIfCDN into that helper so the checks and gologger.Info message are
centralized; in the fast path convert the [4]byte+string IP to a string before
calling shouldSkip and preserve existing behavior/short-circuits (return true to
indicate skip, false to proceed).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 724dccb5-0d09-4885-9a72-7955c1cd82ac

📥 Commits

Reviewing files that changed from the base of the PR and between 3d56d22 and 57df542.

📒 Files selected for processing (13)
  • README.md
  • pkg/prediction/default_model.go
  • pkg/prediction/model.go
  • pkg/prediction/model_test.go
  • pkg/result/results.go
  • pkg/runner/fastsend.go
  • pkg/runner/fastsend_test.go
  • pkg/runner/options.go
  • pkg/runner/runner.go
  • pkg/runner/smartscan.go
  • pkg/runner/validate.go
  • pkg/scan/scan_raw.go
  • pkg/scan/scan_type_test.go

@ehsandeep ehsandeep linked an issue Mar 28, 2026 that may be closed by this pull request
@Mzack9999 Mzack9999 marked this pull request as ready for review March 31, 2026 18:12
@auto-assign auto-assign bot requested a review from dogancanbakir March 31, 2026 18:12
@Mzack9999 Mzack9999 requested a review from ehsandeep March 31, 2026 18:12
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
pkg/runner/fastsend.go (1)

177-193: Keep the CIDR lookup out of the linear hot path.

pickIPv4 walks t.entries from the start on every probe, so a target list with many /32s or small CIDRs turns this back into O(len(targets)) work in the hottest loop. Consider storing cumulative end offsets in buildTargetIndex and using sort.Search so the fast path stays cheap regardless of target shape.

Also applies to: 204-220

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/runner/fastsend.go` around lines 177 - 193, The hot path currently
linear-scans targetIndex.entries in pickIPv4; modify buildTargetIndex to compute
and store cumulative end offsets (e.g., an []int64 cumulativeEnds or end field
on indexEntry) as you append entries and set idx.total, then change pickIPv4 to
use sort.Search (binary search) over those cumulative ends to pick the target by
offset instead of iterating from the start; update references to
targetIndex.entries and any uses of idx.total or indexEntry so the selection
uses the new cumulative data for O(log n) lookups.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/runner/fastsend.go`:
- Around line 44-59: The code currently derives srcIP by calling
scan.PkgRouter.Route(net.IPv4(1,1,1,1)), which bakes a single address into
baseSum and breaks TCP checksums on multihomed/non-default-route hosts; instead,
stop using Route(1.1.1.1) and pick a fixed local IPv4 for checksum calculation
from handler.SourceIp4 if set, then from the bound connection address via
handler.TcpConn4.LocalAddr() (extract the IP and To4()), and only if neither is
available fall back to the standard sender behavior (return errNoSourceIP or use
the existing sender path). Update the srcIP computation and remove the
PkgRouter.Route fallback so baseSum is computed from a stable, connection-bound
source IP.
- Around line 129-154: parseIPv4Fast currently accepts dots even when no digits
were read for the current octet; update parseIPv4Fast to reject empty octets by
tracking whether at least one digit was seen for the current octet (e.g., a
sawDigit boolean set to true on digit) and on encountering '.' ensure sawDigit
is true before assigning ip[dots]=byte(octet), then reset sawDigit and octet for
the next octet; also verify sawDigit is true before assigning the final octet
(ip[3]) and return false if not.

---

Nitpick comments:
In `@pkg/runner/fastsend.go`:
- Around line 177-193: The hot path currently linear-scans targetIndex.entries
in pickIPv4; modify buildTargetIndex to compute and store cumulative end offsets
(e.g., an []int64 cumulativeEnds or end field on indexEntry) as you append
entries and set idx.total, then change pickIPv4 to use sort.Search (binary
search) over those cumulative ends to pick the target by offset instead of
iterating from the start; update references to targetIndex.entries and any uses
of idx.total or indexEntry so the selection uses the new cumulative data for
O(log n) lookups.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 841af42b-8b6c-4ccf-89fa-d9d5bc883558

📥 Commits

Reviewing files that changed from the base of the PR and between 57df542 and 859e81a.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (5)
  • go.mod
  • pkg/prediction/model.go
  • pkg/runner/fastsend.go
  • pkg/runner/options.go
  • pkg/runner/smartscan.go
✅ Files skipped from review due to trivial changes (1)
  • go.mod
🚧 Files skipped from review as they are similar to previous changes (3)
  • pkg/runner/options.go
  • pkg/prediction/model.go
  • pkg/runner/smartscan.go

Comment on lines +44 to +59
var srcIP net.IP
if handler.SourceIp4 != nil {
srcIP = handler.SourceIp4
} else if scan.PkgRouter != nil {
var routeErr error
_, _, srcIP, routeErr = scan.PkgRouter.Route(net.IPv4(1, 1, 1, 1))
if routeErr != nil {
return nil, fmt.Errorf("route lookup failed: %w", routeErr)
}
}
if srcIP == nil {
return nil, errNoSourceIP
}
src4 := srcIP.To4()
if src4 == nil {
return nil, errNoSourceIP
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't derive the checksum source IP from Route(1.1.1.1).

This address is baked into baseSum and reused for every SYN. On multihomed hosts or non-default-route scans, the kernel can choose a different source IP for the real destination, which makes the TCP checksum invalid and silently drops probes. Prefer a fixed local address from handler.SourceIp4 or the bound handler.TcpConn4.LocalAddr(), and fall back to the standard sender when that is not known.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/runner/fastsend.go` around lines 44 - 59, The code currently derives
srcIP by calling scan.PkgRouter.Route(net.IPv4(1,1,1,1)), which bakes a single
address into baseSum and breaks TCP checksums on multihomed/non-default-route
hosts; instead, stop using Route(1.1.1.1) and pick a fixed local IPv4 for
checksum calculation from handler.SourceIp4 if set, then from the bound
connection address via handler.TcpConn4.LocalAddr() (extract the IP and To4()),
and only if neither is available fall back to the standard sender behavior
(return errNoSourceIP or use the existing sender path). Update the srcIP
computation and remove the PkgRouter.Route fallback so baseSum is computed from
a stable, connection-bound source IP.

Comment on lines +129 to +154
func parseIPv4Fast(s string) (ip [4]byte, ok bool) {
var octet, dots int
for i := 0; i < len(s); i++ {
c := s[i]
switch {
case c >= '0' && c <= '9':
octet = octet*10 + int(c-'0')
if octet > 255 {
return ip, false
}
case c == '.':
if dots >= 3 {
return ip, false
}
ip[dots] = byte(octet)
dots++
octet = 0
default:
return ip, false
}
}
if dots != 3 {
return ip, false
}
ip[3] = byte(octet)
return ip, true
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Reject empty octets in parseIPv4Fast.

Inputs like .1.2.3, 1..2.3, and 1.2.3. currently parse successfully because . is accepted even when no digits were seen for the current octet. That violates the function contract and can silently rewrite malformed targets.

Proposed fix
 func parseIPv4Fast(s string) (ip [4]byte, ok bool) {
-	var octet, dots int
+	var octet, dots int
+	var hasDigit bool
 	for i := 0; i < len(s); i++ {
 		c := s[i]
 		switch {
 		case c >= '0' && c <= '9':
 			octet = octet*10 + int(c-'0')
 			if octet > 255 {
 				return ip, false
 			}
+			hasDigit = true
 		case c == '.':
-			if dots >= 3 {
+			if dots >= 3 || !hasDigit {
 				return ip, false
 			}
 			ip[dots] = byte(octet)
 			dots++
 			octet = 0
+			hasDigit = false
 		default:
 			return ip, false
 		}
 	}
-	if dots != 3 {
+	if dots != 3 || !hasDigit {
 		return ip, false
 	}
 	ip[3] = byte(octet)
 	return ip, true
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func parseIPv4Fast(s string) (ip [4]byte, ok bool) {
var octet, dots int
for i := 0; i < len(s); i++ {
c := s[i]
switch {
case c >= '0' && c <= '9':
octet = octet*10 + int(c-'0')
if octet > 255 {
return ip, false
}
case c == '.':
if dots >= 3 {
return ip, false
}
ip[dots] = byte(octet)
dots++
octet = 0
default:
return ip, false
}
}
if dots != 3 {
return ip, false
}
ip[3] = byte(octet)
return ip, true
func parseIPv4Fast(s string) (ip [4]byte, ok bool) {
var octet, dots int
var hasDigit bool
for i := 0; i < len(s); i++ {
c := s[i]
switch {
case c >= '0' && c <= '9':
octet = octet*10 + int(c-'0')
if octet > 255 {
return ip, false
}
hasDigit = true
case c == '.':
if dots >= 3 || !hasDigit {
return ip, false
}
ip[dots] = byte(octet)
dots++
octet = 0
hasDigit = false
default:
return ip, false
}
}
if dots != 3 || !hasDigit {
return ip, false
}
ip[3] = byte(octet)
return ip, true
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/runner/fastsend.go` around lines 129 - 154, parseIPv4Fast currently
accepts dots even when no digits were read for the current octet; update
parseIPv4Fast to reject empty octets by tracking whether at least one digit was
seen for the current octet (e.g., a sawDigit boolean set to true on digit) and
on encountering '.' ensure sawDigit is true before assigning
ip[dots]=byte(octet), then reset sawDigit and octet for the next octet; also
verify sawDigit is true before assigning the final octet (ip[3]) and return
false if not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Predictive port scanning

1 participant