Skip to content

Conversation

@visz11
Copy link

@visz11 visz11 commented Oct 16, 2025

User description


CodeAnt-AI Description

Add NETWORK_DIRECTION function to ESQL and centralize network direction logic

What Changed

  • Adds a new ESQL function NETWORK_DIRECTION (alias NETDIR) that returns one of: inbound, outbound, internal, external when given a source IP, destination IP, and a list of internal networks.
  • Unifies network classification by moving the IP/network checks into a shared NetworkDirectionUtils used by both the ESQL function and the ingest pipeline processor, so query-time and ingest-time results match.
  • The function returns null when required inputs are missing, when the networks list is empty, or when inputs are invalid; invalid network strings now emit warnings and yield null results rather than misleading labels.
  • Adds a generated evaluator for the function, extensive unit tests for the utils, processor and ESQL function, CSV QA examples, documentation snippets and a capability flag enabling the function.

Impact

✅ Network direction available in ESQL queries (NETWORK_DIRECTION / NETDIR)
✅ Consistent inbound/outbound/internal/external classification in ESQL and ingest pipelines
✅ Invalid or multi-valued inputs produce warnings and null results

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Summary by CodeRabbit

  • New Features
    • Added network_direction ESQL function (alias: netdir) that classifies network traffic direction (inbound, outbound, internal, external) based on source IP, destination IP, and internal network definitions.
    • Added comprehensive documentation and examples for the new function with multiple test scenarios.

MattAlp and others added 30 commits October 2, 2025 14:21
@codeant-ai
Copy link

codeant-ai bot commented Oct 16, 2025

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@gemini-code-assist
Copy link

Summary of Changes

Hello @visz11, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant enhancement to ESQL by adding a network_direction function. This function allows users to easily classify network traffic flow, which is crucial for security analysis and network monitoring. The implementation involved creating a dedicated utility class for network logic, ensuring consistency and reusability, and included a necessary fix in the code generation process to support the new function effectively. The changes are fully documented and thoroughly tested.

Highlights

  • New ESQL Function: Implemented the network_direction function in ESQL, which determines the direction of network traffic (inbound, outbound, internal, external) based on source IP, destination IP, and a list of internal networks.
  • Code Refactoring: Extracted network direction logic into a new utility class, NetworkDirectionUtils, to centralize and reuse the functionality previously embedded in NetworkDirectionProcessor.
  • Bug Fix: Addressed a small codegen (Poet) bug in BlockArgument.java to ensure proper evaluation of the new function.
  • Documentation and Testing: Added comprehensive documentation, Kibana definitions, and extensive test cases for the new network_direction function, including error handling and various network scenarios.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link

coderabbitai bot commented Oct 16, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This PR implements a new network_direction ESQL function that classifies network traffic direction (inbound, outbound, internal, external) based on source IP, destination IP, and internal networks. The implementation introduces a shared utility class, refactors an existing ingest processor, and integrates the function into ESQL with comprehensive tests and documentation.

Changes

Cohort / File(s) Summary
Documentation & Configuration
docs/changelog/136133.yaml
Changelog entry documenting the network_direction function enhancement.
ESQL Function Documentation
docs/reference/query-languages/esql/_snippets/functions/description/network_direction.md, docs/reference/query-languages/esql/_snippets/functions/examples/network_direction.md, docs/reference/query-languages/esql/_snippets/functions/layout/network_direction.md, docs/reference/query-languages/esql/_snippets/functions/parameters/network_direction.md, docs/reference/query-languages/esql/_snippets/functions/types/network_direction.md
Auto-generated documentation snippets for the function including description, examples, parameters, type signatures, and page layout.
Kibana ESQL Reference
docs/reference/query-languages/esql/kibana/definition/functions/network_direction.json, docs/reference/query-languages/esql/kibana/docs/functions/network_direction.md
Function definition and documentation artifacts for Kibana ESQL reference, defining two overload signatures and usage examples.
Network Direction Utility
server/src/main/java/org/elasticsearch/common/network/NetworkDirectionUtils.java
New utility class providing IP network classification: determines internality of addresses against named networks/CIDR ranges and derives direction labels (internal, external, inbound, outbound) from source/destination internality.
Network Direction Utility Tests
server/src/test/java/org/elasticsearch/common/network/NetworkDirectionUtilsTests.java
Test suite covering CIDR-based logic, unspecified addresses, and private/public network classification.
Ingest Processor Refactoring
modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/NetworkDirectionProcessor.java
Refactored to delegate network direction logic to NetworkDirectionUtils; removed custom IP analysis code and constants.
Ingest Processor Tests
modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/NetworkDirectionProcessorTests.java
Minor comment addition; no functional changes.
ESQL Function Implementation
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirection.java
New ESQL scalar function implementing network direction classification with evaluator logic, type resolution, and serialization support.
ESQL Registry & Integration
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java, x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java, x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ScalarFunctionWritables.java
Registers network_direction function in ESQL, adds capability flag, and integrates into serialization framework.
ESQL Function Tests
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirectionTests.java, x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirectionErrorTests.java, x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirectionSerializationTests.java
Test classes covering function evaluation, type validation, and serialization behavior.
ESQL Test Fixtures
x-pack/plugin/esql/qa/testFixtures/src/main/resources/ip.csv-spec
Extended test suite with scenarios covering simple evaluation, aliases, inline networks, IP prefix usage, and error handling.

Sequence Diagram

sequenceDiagram
    participant User as ESQL Query
    participant NDF as NetworkDirection<br/>Function
    participant NDU as NetworkDirectionUtils
    participant IP as IP Classification<br/>Logic

    User->>NDF: network_direction(src_ip, dst_ip, networks)
    NDF->>NDF: toEvaluator()
    NDF->>NDU: isInternal(networks, src_ip)
    NDU->>IP: Check IP against<br/>named networks/CIDR
    IP-->>NDU: source is internal?
    NDF->>NDU: isInternal(networks, dst_ip)
    NDU->>IP: Check IP against<br/>named networks/CIDR
    IP-->>NDU: destination is internal?
    NDF->>NDU: getDirection(src_internal,<br/>dst_internal)
    NDU-->>NDF: inbound/outbound/<br/>internal/external
    NDF-->>User: direction (keyword)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Rationale: The PR spans multiple interconnected concerns—a new utility class with substantial IP classification logic, a new ESQL scalar function with evaluators and type resolution, refactoring of an existing ingest processor, registry/capability integration, and extensive test coverage across 8+ test files. While many changes follow consistent patterns, the density of new logic (NetworkDirectionUtils), the ESQL function's evaluator implementation, and the variety of integration points (registry, writables, capabilities) require varied reasoning for each major component.

Poem

🐰 Through networks we hop, from source to destination,
Classifying each packet with precision and care.
Internal or external, the direction we trace,
With CIDR and IP ranges in this digital space—
Elasticsearch's network, now faster to face! 🌐

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.13% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "ESQL: Implement network_direction function" accurately and directly reflects the primary change in this changeset. The title specifies both the context (ESQL) and the exact deliverable (the network_direction function), which aligns with the PR objectives stating the implementation of the network_direction(source_ip, destination_ip, networks) ESQL function. The title is concise, uses clear language without unnecessary noise, and is specific enough that a developer scanning the git history would immediately understand the core contribution. All changes in the PR—including the new scalar function class, documentation snippets, utility classes, test fixtures, and function registry updates—are clearly components of this singular main objective.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch clone-network_direction_esql_function

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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.

@codeant-ai codeant-ai bot added the size:XXL This PR changes 1000+ lines, ignoring generated files label Oct 16, 2025
@visz11
Copy link
Author

visz11 commented Oct 16, 2025

/refacto-visz

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces the network_direction function in ESQL, which is a great addition. The implementation correctly reuses logic from the existing NetworkDirectionProcessor by extracting it into a new NetworkDirectionUtils class. The changes are well-tested with new integration and unit tests.

I've found a few opportunities for performance improvements in the new utility class and the ESQL function's evaluator. These relate to avoiding repeated IP string parsing and optimizing loops. My detailed comments are below.

Overall, this is a solid contribution. Addressing the performance points will make it even better.

Comment on lines +38 to +45
public static boolean isInternal(List<String> networks, String ip) {
for (String network : networks) {
if (inNetwork(InetAddresses.forString(ip), network)) {
return true;
}
}
return false;
}

Choose a reason for hiding this comment

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

high

The isInternal method repeatedly parses the IP string inside the for loop by calling InetAddresses.forString(ip). This is inefficient, especially if the networks list is large. The IP string should be parsed into an InetAddress object once before the loop.

Suggested change
public static boolean isInternal(List<String> networks, String ip) {
for (String network : networks) {
if (inNetwork(InetAddresses.forString(ip), network)) {
return true;
}
}
return false;
}
public static boolean isInternal(List<String> networks, String ip) {
InetAddress address = InetAddresses.forString(ip);
for (String network : networks) {
if (inNetwork(address, network)) {
return true;
}
}
return false;
}

Comment on lines +56 to +58
case PRIVATE_NAMED_NETWORK -> isPrivate(NetworkAddress.format(address));
case PUBLIC_NAMED_NETWORK -> isPublic(NetworkAddress.format(address));
default -> CIDRUtils.isInRange(NetworkAddress.format(address), network);

Choose a reason for hiding this comment

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

medium

There's a performance issue here due to InetAddress to String conversion. The inNetwork method accepts an InetAddress, but for private, public, and CIDR checks, it converts it back to a String using NetworkAddress.format(address). The downstream methods (isPrivate, isPublic, CIDRUtils.isInRange) then have to parse this string back into an InetAddress. This round-trip is inefficient.

Consider refactoring isPrivate, isPublic, and other helpers to work directly with InetAddress objects to avoid these unnecessary conversions. If CIDRUtils methods only accept strings, you might need to add overloads there or pass both the InetAddress and its string representation down.

Copy link
Author

Choose a reason for hiding this comment

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

@coderabbitai what do you think is this comment valid

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the implementation of isPrivate and isPublic methods in NetworkDirectionUtils
cat server/src/main/java/org/elasticsearch/common/network/NetworkDirectionUtils.java

Unrecoverable error: Failed to clone repository


🏁 Script executed:

#!/bin/bash
# Find and examine CIDRUtils class implementation
fd -t f "CIDRUtils.java" --exec cat {}

Unrecoverable error: Failed to clone repository


🏁 Script executed:

#!/bin/bash
# Find and examine NetworkAddress.format method
fd -t f "NetworkAddress.java" --exec cat {}

Unrecoverable error: Failed to clone repository


🌐 Web query:

Elasticsearch NetworkAddress.format method what does it return

💡 Result:

The NetworkAddress.format methods return a human-friendly String representation of the address (or address+port/port-range). IPv4 prints as normal (e.g. "127.0.0.1"); IPv6 is compressed and scope ids are removed (e.g. "::1"). When a port is included the port is appended (IPv6 is bracketed) — e.g. "127.0.0.1:9300" or "[::1]:9300". For a PortsRange the range is shown in brackets (e.g. "127.0.0.1:[9300-9400]"). Any hostname information is ignored. [1][2]

Sources: Elasticsearch API docs and source javadoc. [1] (artifacts.elastic.co) [2] (fossies.org)

Citations:


🌐 Web query:

Java InetAddress to String conversion performance overhead

💡 Result:

Short answer: converting an InetAddress to a String is cheap when no name resolution is required, but can be expensive (blocks on network/DNS) if the call triggers a reverse DNS lookup for the hostname. Use getHostAddress() (or format the raw address bytes) when you only need the numeric IP to avoid DNS latency; avoid getHostName()/getCanonicalHostName() in hot paths unless you control caching/TTL. [1][2][3]

Supporting details (very brief)

  • InetAddress.toString() returns "hostname / literal IP". If the InetAddress already contains a hostname no lookup is performed; otherwise toString will not perform a reverse lookup. getHostName() or getCanonicalHostName() may perform reverse DNS and block. [1]
  • DNS lookups are network calls and can be slow; JVM DNS caching (networkaddress.cache.*) affects how often lookups happen but will not eliminate the initial cost. Many reports/answers show getHostName/getCanonicalHostName can be the source of large latency in real apps. [1][2][3]

Practical recommendations

  • If you only need the IP: call getHostAddress() (or construct the string from getAddress()).
  • If you need a hostname but must avoid blocking, resolve once asynchronously and cache results yourself or tune JVM DNS cache TTLs.
  • For microbenchmarks, test on your environment (DNS, JVM version, cache settings) because behavior and caching defaults can vary. [1][2]

Sources:
[1] Oracle InetAddress Javadoc (toString/getHostName and caching).
[2] StackOverflow discussion: InetAddress.getHostName() slow / causes network lookup.
[3] Analysis/demo of JVM reverse-lookup/caching behavior.


🏁 Script executed:

#!/bin/bash
# Search for isPrivate and isPublic method definitions in NetworkDirectionUtils
rg -n "private static.*isPrivate|private static.*isPublic" --type java -A 10

Unrecoverable error: Failed to clone repository


🏁 Script executed:

#!/bin/bash
# Search for CIDRUtils.isInRange method signature and implementation
rg -n "isInRange" --type java -A 5 -B 2

Unrecoverable error: Failed to clone repository


🏁 Script executed:

#!/bin/bash
# Look for InetAddresses.forString usage to understand parsing patterns
rg -n "InetAddresses\.forString" --type java -C 3

Unrecoverable error: Failed to clone repository


@visz11 The comment raises a structurally valid concern about potential inefficiency, though I cannot definitively confirm the full impact without seeing the implementations of isPrivate(), isPublic(), and CIDRUtils.isInRange().

Here's what makes the concern valid:

  1. Inconsistent pattern: Most branches (isLoopback, isUnicast, etc.) work directly with InetAddress, but three cases convert to String

  2. Double conversion in the call chain:

    • isInternal() receives a String IP
    • Converts it to InetAddress (line 40)
    • For private/public/CIDR cases, converts back to String (line 56, 57, 58)
    • If those methods then parse back to InetAddress, that's wasteful
  3. Performance consideration: While InetAddress.getHostAddress() is relatively fast for numeric IPs (no DNS lookup), unnecessary String↔InetAddress round-trips add allocation and parsing overhead, especially in hot paths

To verify and address this:

  1. Check if isPrivate(), isPublic(), and CIDRUtils.isInRange() parse their String arguments back to InetAddress
  2. If they do, consider refactoring to accept InetAddress directly
  3. If CIDRUtils only has String-based APIs, evaluate whether adding InetAddress overloads would benefit performance

The architectural point about minimizing conversions is sound engineering practice, particularly for utility methods that may be called frequently. I'd recommend investigating the downstream implementations to determine if refactoring is warranted.

Comment on lines +157 to +171
boolean sourceInternal = false;
boolean destinationInternal = false;

for (int i = first; i < first + valueCount; i++) {
if (NetworkDirectionUtils.inNetwork(sourceIpAddress, networks.getBytesRef(i, netScratch).utf8ToString())) {
sourceInternal = true;
break;
}
}
for (int i = first; i < first + valueCount; i++) {
if (NetworkDirectionUtils.inNetwork(destinationIpAddress, networks.getBytesRef(i, netScratch).utf8ToString())) {
destinationInternal = true;
break;
}
}

Choose a reason for hiding this comment

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

medium

The logic to determine if the source and destination IPs are internal involves two separate loops over the networks block. This is inefficient as it iterates through the list of networks twice in the worst case. You can combine these two loops into a single one to improve performance.

Suggested change
boolean sourceInternal = false;
boolean destinationInternal = false;
for (int i = first; i < first + valueCount; i++) {
if (NetworkDirectionUtils.inNetwork(sourceIpAddress, networks.getBytesRef(i, netScratch).utf8ToString())) {
sourceInternal = true;
break;
}
}
for (int i = first; i < first + valueCount; i++) {
if (NetworkDirectionUtils.inNetwork(destinationIpAddress, networks.getBytesRef(i, netScratch).utf8ToString())) {
destinationInternal = true;
break;
}
}
boolean sourceInternal = false;
boolean destinationInternal = false;
for (int i = first; i < first + valueCount; i++) {
String network = networks.getBytesRef(i, netScratch).utf8ToString();
if (sourceInternal == false && NetworkDirectionUtils.inNetwork(sourceIpAddress, network)) {
sourceInternal = true;
}
if (destinationInternal == false && NetworkDirectionUtils.inNetwork(destinationIpAddress, network)) {
destinationInternal = true;
}
if (sourceInternal && destinationInternal) {
break;
}
}

@refacto-visz
Copy link

refacto-visz bot commented Oct 16, 2025

ESQL: Implement network_direction function

TL;DR: Adds ESQL network_direction function to classify network traffic direction based on source/destination IPs and internal network definitions.


Refacto PR Summary

Implements network_direction function for ESQL query language, enabling network traffic classification as internal, external, inbound, or outbound.
Extracts network direction logic from ingest processor into shared utilities, adds comprehensive ESQL function with evaluator generation, and includes extensive test coverage. This PR introduces the network_direction function to ESQL, allowing users to classify network traffic direction by analyzing source and destination IP addresses against defined internal networks. The implementation extracts existing logic from the NetworkDirectionProcessor into a shared NetworkDirectionUtils class, creates a new ESQL scalar function with auto-generated evaluators, and adds comprehensive support for IPv4/IPv6 addresses, CIDR notation, and named network ranges like "private", "loopback", and "public".

Change Highlights

Click to expand
  • server/src/main/java/org/elasticsearch/common/network/NetworkDirectionUtils.java: New shared utility class with network classification logic
  • x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirection.java: ESQL function implementation with IP validation
  • x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirectionEvaluator.java: Auto-generated evaluator for function execution
  • modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/NetworkDirectionProcessor.java: Refactored to use shared utilities
  • x-pack/plugin/esql/qa/testFixtures/src/main/resources/ip.csv-spec: Comprehensive test cases with various network scenarios
  • x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java: Feature flag for network_direction capability
  • x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/argument/BlockArgument.java: Fixed codegen bug for block-style parameters

Sequence Diagram

sequenceDiagram
    participant Q as ESQL Query
    participant E as NetworkDirectionEvaluator
    participant U as NetworkDirectionUtils
    participant N as Network Classifier
    
    Q->>E: NETWORK_DIRECTION(source_ip, dest_ip, networks)
    E->>E: Validate IP parameters
    E->>E: Extract network list from block
    E->>U: isInternal(networks, source_ip)
    U->>N: Check against CIDR/named networks
    N-->>U: Boolean result
    U-->>E: Source internal status
    E->>U: isInternal(networks, dest_ip)
    U->>N: Check against CIDR/named networks
    N-->>U: Boolean result
    U-->>E: Destination internal status
    E->>U: getDirection(sourceInternal, destInternal)
    U-->>E: Direction (internal/external/inbound/outbound)
    E-->>Q: Keyword result
Loading

Testing Guide

Click to expand
  1. Basic CIDR classification: Test NETWORK_DIRECTION("10.0.1.1"::ip, "192.168.1.2"::ip, ["10.0.0.0/8"]) returns "outbound"
  2. Named network ranges: Verify NETWORK_DIRECTION("127.0.0.1"::ip, "8.8.8.8"::ip, ["loopback", "private"]) returns "outbound"
  3. Internal traffic: Test same network IPs with private networks return "internal"
  4. IPv6 support: Validate function works with IPv6 addresses and fd00::/8 private ranges
  5. Error handling: Test invalid network definitions trigger appropriate warnings and null results
  6. Multi-value networks: Verify function correctly processes arrays of network definitions

@refacto-visz
Copy link

refacto-visz bot commented Oct 16, 2025

Refacto is reviewing this PR. Please wait for the review comments to be posted.

Copy link

@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: 1

🧹 Nitpick comments (6)
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirectionTests.java (1)

33-98: Add multi-valued networks and type-acceptance coverage

  • Include cases where internal_networks contains multiple entries (e.g., ["private","10.0.0.0/8"]) to exercise MV iteration and short-circuiting.
  • Please verify that iterating over DataType.stringTypes() won’t introduce TEXT that fails the function’s isStringAndExact resolution. If TEXT isn’t “exact” here, constrain tests to KEYWORD.
server/src/main/java/org/elasticsearch/common/network/NetworkDirectionUtils.java (3)

47-60: Validate named-network semantics: global_unicast vs unicast, and public including multicast

  • GLOBAL_UNICAST currently maps to isUnicast(address), same as UNICAST. Typically, “global unicast” should exclude private/ULA ranges. Consider:
    • case GLOBAL_UNICAST_NAMED_NETWORK -> isUnicast(address) && isPublic(NetworkAddress.format(address));
  • PUBLIC uses isPublic(String) which doesn’t exclude non-local multicast. If “public” is intended to mean publicly routable unicast, consider excluding multicast:
    • return isLocalOrPrivate(ip) == false && isMulticast(InetAddresses.forString(ip)) == false;

Please verify against the ingest processor docs and prior behavior.


108-116: IPv6 private range may need fc00::/7, not only fd00::/8

Current private IPv6 check only includes fd00::/8. ULA is defined as fc00::/7. If intentional to exclude fc00::/8, please document it; otherwise expand the range.


47-60: Be tolerant to whitespace/case in named networks

Users may provide names with varying case/extra spaces. To harden, normalize network before the switch (trim + toLowerCase(Locale.ROOT)).

Apply within inNetwork:

-    public static boolean inNetwork(InetAddress address, String network) {
-        return switch (network) {
+    public static boolean inNetwork(InetAddress address, String network) {
+        final String n = network == null ? "" : network.trim().toLowerCase(java.util.Locale.ROOT);
+        return switch (n) {
             case LOOPBACK_NAMED_NETWORK -> isLoopback(address);
             ...
modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/NetworkDirectionProcessor.java (1)

121-125: Normalize network names before classification

Trim and lowercase the configured networks to avoid case/whitespace issues for named networks; CIDR ranges are unaffected.

-        boolean sourceInternal = NetworkDirectionUtils.isInternal(networks, sourceIpAddrString);
-        boolean destinationInternal = NetworkDirectionUtils.isInternal(networks, destIpAddrString);
+        final List<String> normalized = networks.stream()
+            .map(s -> s == null ? null : s.trim())
+            .filter(s -> s != null && s.isEmpty() == false)
+            .map(s -> s.toLowerCase(java.util.Locale.ROOT))
+            .toList();
+        boolean sourceInternal = NetworkDirectionUtils.isInternal(normalized, sourceIpAddrString);
+        boolean destinationInternal = NetworkDirectionUtils.isInternal(normalized, destIpAddrString);
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirection.java (1)

120-133: Optional: avoid per-row allocations by passing fixed 4/16 byte buffers

To keep zero-GC, pass two fixed buffers into the evaluator and select by length.

-        return new NetworkDirectionEvaluator.Factory(
+        return new NetworkDirectionEvaluator.Factory(
             source(),
-            context -> new BytesRef(16),
+            context -> new byte[16],   // scratch16
+            context -> new byte[4],    // scratch4
             context -> new BytesRef(),
             sourceIpEvaluatorSupplier,
             destinationIpEvaluatorSupplier,
             internalNetworksEvaluatorSupplier
         );

And update the @evaluator signature and IP decoding:

-    static void process(
-        BytesRefBlock.Builder builder,
-        @Fixed(includeInToString = false, scope = THREAD_LOCAL) BytesRef scratch,
-        @Fixed(includeInToString = false, scope = THREAD_LOCAL) BytesRef netScratch,
+    static void process(
+        BytesRefBlock.Builder builder,
+        @Fixed(includeInToString = false, scope = THREAD_LOCAL) byte[] scratch16,
+        @Fixed(includeInToString = false, scope = THREAD_LOCAL) byte[] scratch4,
+        @Fixed(includeInToString = false, scope = THREAD_LOCAL) BytesRef netScratch,
         BytesRef sourceIp,
         BytesRef destinationIp,
         @Position int position,
         BytesRefBlock networks
     ) {
-        System.arraycopy(sourceIp.bytes, sourceIp.offset, scratch.bytes, 0, sourceIp.length);
-        InetAddress sourceIpAddress = InetAddressPoint.decode(scratch.bytes);
+        byte[] sbuf = sourceIp.length == 4 ? scratch4 : scratch16;
+        System.arraycopy(sourceIp.bytes, sourceIp.offset, sbuf, 0, sourceIp.length);
+        InetAddress sourceIpAddress = InetAddressPoint.decode(sbuf);
-        System.arraycopy(destinationIp.bytes, destinationIp.offset, scratch.bytes, 0, destinationIp.length);
-        InetAddress destinationIpAddress = InetAddressPoint.decode(scratch.bytes);
+        byte[] dbuf = destinationIp.length == 4 ? scratch4 : scratch16;
+        System.arraycopy(destinationIp.bytes, destinationIp.offset, dbuf, 0, destinationIp.length);
+        InetAddress destinationIpAddress = InetAddressPoint.decode(dbuf);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9371a61 and 18e39e6.

⛔ Files ignored due to path filters (3)
  • docs/reference/query-languages/esql/images/functions/network_direction.svg is excluded by !**/*.svg
  • x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/argument/BlockArgument.java is excluded by !**/gen/**
  • x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirectionEvaluator.java is excluded by !**/generated/**
📒 Files selected for processing (20)
  • docs/changelog/136133.yaml (1 hunks)
  • docs/reference/query-languages/esql/_snippets/functions/description/network_direction.md (1 hunks)
  • docs/reference/query-languages/esql/_snippets/functions/examples/network_direction.md (1 hunks)
  • docs/reference/query-languages/esql/_snippets/functions/layout/network_direction.md (1 hunks)
  • docs/reference/query-languages/esql/_snippets/functions/parameters/network_direction.md (1 hunks)
  • docs/reference/query-languages/esql/_snippets/functions/types/network_direction.md (1 hunks)
  • docs/reference/query-languages/esql/kibana/definition/functions/network_direction.json (1 hunks)
  • docs/reference/query-languages/esql/kibana/docs/functions/network_direction.md (1 hunks)
  • modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/NetworkDirectionProcessor.java (2 hunks)
  • modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/NetworkDirectionProcessorTests.java (1 hunks)
  • server/src/main/java/org/elasticsearch/common/network/NetworkDirectionUtils.java (1 hunks)
  • server/src/test/java/org/elasticsearch/common/network/NetworkDirectionUtilsTests.java (1 hunks)
  • x-pack/plugin/esql/qa/testFixtures/src/main/resources/ip.csv-spec (1 hunks)
  • x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java (2 hunks)
  • x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java (2 hunks)
  • x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ScalarFunctionWritables.java (2 hunks)
  • x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirection.java (1 hunks)
  • x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirectionErrorTests.java (1 hunks)
  • x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirectionSerializationTests.java (1 hunks)
  • x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirectionTests.java (1 hunks)
🔇 Additional comments (19)
modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/NetworkDirectionProcessorTests.java (1)

96-96: LGTM! Helpful cross-reference comment.

The comment appropriately documents that test data is copied from NetworkDirectionUtils tests, improving maintainability by clarifying the relationship between the two test suites.

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java (2)

1504-1507: LGTM! Properly gated capability addition.

The NETWORK_DIRECTION capability is correctly:

  • Snapshot-gated with Build.current().isSnapshot()
  • Documented with a clear description
  • Placed logically within the enum sequence

1517-1517: LGTM! Correct enum termination.

The semicolon terminator is properly updated to reflect the addition of the new capability constant.

x-pack/plugin/esql/qa/testFixtures/src/main/resources/ip.csv-spec (1)

733-825: LGTM! Comprehensive test coverage for network_direction function.

The test fixtures cover:

  • ✓ Basic direction classification (internal, inbound, outbound, external)
  • ✓ Function alias (NETDIR)
  • ✓ Inline network arrays
  • ✓ Integration with IP_PREFIX function
  • ✓ Null IP handling
  • ✓ Invalid network input with proper warning patterns

The tests follow established patterns and include proper capability gating with required_capability: network_direction.

docs/changelog/136133.yaml (1)

1-6: LGTM! Properly formatted changelog entry.

The changelog entry correctly documents the enhancement with:

  • Clear summary describing the function implementation
  • Appropriate area classification (ES|QL)
  • Correct type (enhancement)
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ScalarFunctionWritables.java (2)

28-28: LGTM! Import properly placed.

The NetworkDirection import is correctly positioned in alphabetical order within the ip package imports.


102-102: LGTM! Registry entry correctly added.

The NetworkDirection.ENTRY is properly registered in the NamedWriteableRegistry list, maintaining alphabetical order (between Md5 and Now).

docs/reference/query-languages/esql/_snippets/functions/examples/network_direction.md (1)

1-14: LGTM! Auto-generated documentation example.

The example demonstrates typical NETWORK_DIRECTION usage with:

  • IP casting syntax ("127.0.0.1"::ip)
  • Inline network array (["loopback", "private"])
  • Clear expected output showing the direction result

The file includes proper notice that it's auto-generated by ESQL's test framework.

docs/reference/query-languages/esql/kibana/docs/functions/network_direction.md (1)

1-10: LGTM! Auto-generated Kibana documentation.

The documentation clearly describes the NETWORK_DIRECTION function:

  • Accurate description of the four direction types (inbound, outbound, internal, external)
  • Clear parameter explanation (source IP, destination IP, internal networks)
  • Practical example demonstrating usage

The file includes proper notice that it's auto-generated.

docs/reference/query-languages/esql/_snippets/functions/parameters/network_direction.md (1)

1-14: LGTM! Auto-generated parameter documentation.

The parameter documentation is clear and comprehensive:

  • source_ip and destination_ip: Both support IPv4 and IPv6
  • internal_networks: Properly documents support for IPv4/IPv6 addresses, CIDR notation, and named ranges

The file includes proper notice that it's auto-generated.

docs/reference/query-languages/esql/_snippets/functions/layout/network_direction.md (1)

1-24: LGTM! Generated documentation follows standard pattern.

This auto-generated layout file correctly includes all necessary documentation sections (parameters, description, types, examples) and follows the established pattern for ESQL function documentation.

docs/reference/query-languages/esql/_snippets/functions/types/network_direction.md (1)

1-9: LGTM! Type signatures are correct.

The supported types correctly show two overload-like signatures accepting either keyword or text for the internal_networks parameter, while requiring ip types for source and destination, and returning a keyword result.

docs/reference/query-languages/esql/_snippets/functions/description/network_direction.md (1)

1-6: LGTM! Clear and concise description.

The function description accurately explains the purpose and parameters of the network_direction function.

server/src/test/java/org/elasticsearch/common/network/NetworkDirectionUtilsTests.java (1)

19-47: LGTM! Comprehensive test coverage.

The tests effectively cover key scenarios:

  • CIDR notation handling for outbound/inbound classification
  • Special unspecified addresses (0.0.0.0, ::)
  • Private network detection across IPv4 and IPv6
  • Public network filtering

The helper method correctly validates both the intermediate isInternal checks and the final getDirection result.

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirectionErrorTests.java (1)

22-41: LGTM! Error test follows established pattern.

The error test correctly:

  • Integrates with NetworkDirectionTests.parameters() for test cases
  • Constructs NetworkDirection with the proper three arguments
  • Maps argument positions to expected type names ("ip" for positions 0-1, "string" for position 2)

This ensures proper type error messaging for the function.

docs/reference/query-languages/esql/kibana/definition/functions/network_direction.json (1)

1-61: LGTM! Kibana definition is complete and accurate.

The JSON definition correctly specifies:

  • Two signature variants (keyword vs text for internal_networks)
  • Clear parameter descriptions mentioning IPv4/IPv6 support
  • Appropriate return type (keyword)
  • A demonstrative example
  • Correct flags indicating this is a production-ready function
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirectionSerializationTests.java (1)

16-41: LGTM! Serialization test correctly implements required methods.

The test properly:

  • Creates test instances with randomized source and child expressions
  • Mutates each of the three expression fields (sourceIpField, destinationIpField, internalNetworks)
  • Uses randomValueOtherThan to ensure mutations produce different values

This ensures NetworkDirection serialization is thoroughly tested.

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java (2)

114-114: LGTM! Import added in correct location.

The NetworkDirection import is properly placed in the IP functions import section.


463-463: Verify the constructor reference pattern.

Most three-argument functions in this file use the tri() helper (e.g., Clamp at line 348, Top at line 359). This registration uses a direct constructor reference NetworkDirection::new.

Please verify that:

  1. The NetworkDirection class has a constructor matching the expected signature for direct reference registration, OR
  2. The direct constructor reference correctly resolves to a ternary builder

Run the following script to check the NetworkDirection constructor signatures:

Comment on lines +152 to +156
System.arraycopy(sourceIp.bytes, sourceIp.offset, scratch.bytes, 0, sourceIp.length);
InetAddress sourceIpAddress = InetAddressPoint.decode(scratch.bytes);
System.arraycopy(destinationIp.bytes, destinationIp.offset, scratch.bytes, 0, destinationIp.length);
InetAddress destinationIpAddress = InetAddressPoint.decode(scratch.bytes);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: IPv4 decode uses 16-byte buffer → mis-decoding

InetAddressPoint.decode(byte[]) uses the array length (4 vs 16) to determine IPv4 vs IPv6. Copying 4 bytes into a 16-byte buffer and calling decode(scratch.bytes) will decode as IPv6, yielding incorrect results.

Minimal correctness fix:

-        System.arraycopy(sourceIp.bytes, sourceIp.offset, scratch.bytes, 0, sourceIp.length);
-        InetAddress sourceIpAddress = InetAddressPoint.decode(scratch.bytes);
-        System.arraycopy(destinationIp.bytes, destinationIp.offset, scratch.bytes, 0, destinationIp.length);
-        InetAddress destinationIpAddress = InetAddressPoint.decode(scratch.bytes);
+        byte[] srcArr = java.util.Arrays.copyOfRange(
+            sourceIp.bytes, sourceIp.offset, sourceIp.offset + sourceIp.length
+        );
+        InetAddress sourceIpAddress = InetAddressPoint.decode(srcArr);
+        byte[] dstArr = java.util.Arrays.copyOfRange(
+            destinationIp.bytes, destinationIp.offset, destinationIp.offset + destinationIp.length
+        );
+        InetAddress destinationIpAddress = InetAddressPoint.decode(dstArr);

Follow-up: If you want to avoid per-row allocations, I can provide a refactor using two @fixed buffers (4 and 16 bytes) selected by length.

📝 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
System.arraycopy(sourceIp.bytes, sourceIp.offset, scratch.bytes, 0, sourceIp.length);
InetAddress sourceIpAddress = InetAddressPoint.decode(scratch.bytes);
System.arraycopy(destinationIp.bytes, destinationIp.offset, scratch.bytes, 0, destinationIp.length);
InetAddress destinationIpAddress = InetAddressPoint.decode(scratch.bytes);
byte[] srcArr = java.util.Arrays.copyOfRange(
sourceIp.bytes, sourceIp.offset, sourceIp.offset + sourceIp.length
);
InetAddress sourceIpAddress = InetAddressPoint.decode(srcArr);
byte[] dstArr = java.util.Arrays.copyOfRange(
destinationIp.bytes, destinationIp.offset, destinationIp.offset + destinationIp.length
);
InetAddress destinationIpAddress = InetAddressPoint.decode(dstArr);
🤖 Prompt for AI Agents
In
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirection.java
around lines 152-156, the code copies 4-byte IPv4 data into a 16-byte scratch
buffer and calls InetAddressPoint.decode(scratch.bytes), which mis-identifies
IPv4 as IPv6; fix by calling decode with a byte array whose length exactly
equals the IP length (i.e., copy only sourceIp.length/destinationIp.length into
a new byte[length] and pass that trimmed array to InetAddressPoint.decode), or
if an overload exists use a decode variant that accepts offset+length to avoid
the ambiguity; optionally note you can refactor later to use two fixed buffers
(4 and 16 bytes) selected by length to avoid per-row allocations.

Comment on lines +121 to +124
boolean sourceInternal = NetworkDirectionUtils.isInternal(networks, sourceIpAddrString);
boolean destinationInternal = NetworkDirectionUtils.isInternal(networks, destIpAddrString);

private static boolean isLocalOrPrivate(String ip) {
var address = InetAddresses.forString(ip);
return isPrivate(ip)
|| isLoopback(address)
|| isUnspecified(address)
|| isLinkLocalUnicast(address)
|| isLinkLocalMulticast(address)
|| isInterfaceLocalMulticast(address)
|| Arrays.equals(address.getAddress(), BROADCAST_IP4);
return NetworkDirectionUtils.getDirection(sourceInternal, destinationInternal);
Copy link

Choose a reason for hiding this comment

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

Suggestion: Catch and handle potential parsing/validation errors from NetworkDirectionUtils.isInternal (e.g., invalid IP formats) and respect the processor's ignoreMissing behavior by returning null when appropriate, otherwise rethrow the exception. [possible bug]

Suggested change
boolean sourceInternal = NetworkDirectionUtils.isInternal(networks, sourceIpAddrString);
boolean destinationInternal = NetworkDirectionUtils.isInternal(networks, destIpAddrString);
private static boolean isLocalOrPrivate(String ip) {
var address = InetAddresses.forString(ip);
return isPrivate(ip)
|| isLoopback(address)
|| isUnspecified(address)
|| isLinkLocalUnicast(address)
|| isLinkLocalMulticast(address)
|| isInterfaceLocalMulticast(address)
|| Arrays.equals(address.getAddress(), BROADCAST_IP4);
return NetworkDirectionUtils.getDirection(sourceInternal, destinationInternal);
try {
boolean sourceInternal = NetworkDirectionUtils.isInternal(networks, sourceIpAddrString);
boolean destinationInternal = NetworkDirectionUtils.isInternal(networks, destIpAddrString);
return NetworkDirectionUtils.getDirection(sourceInternal, destinationInternal);
} catch (IllegalArgumentException e) {
if (ignoreMissing) {
return null;
}
throw e;
}
Why Change? ⭐

The improved code is a small, local change inside the existing getDirection(...) method and uses only symbols already present in the class: the imported NetworkDirectionUtils and the instance field ignoreMissing. The try block contains the same calls and local variables as the original code, so there is no scope leakage or use of undefined identifiers.

Catching IllegalArgumentException is a conservative choice for handling parsing/validation failures from NetworkDirectionUtils.isInternal (which commonly signals invalid input via IllegalArgumentException). When such an exception occurs, returning null if ignoreMissing is true preserves the processor's existing semantics for missing/invalid inputs (the method already returns null in other missing-value cases). If ignoreMissing is false the exception is rethrown, preserving failure behavior.

The code is syntactically valid Java, compiles in-place (no new imports or signatures required), and does not introduce new checked exceptions. Assumptions: NetworkDirectionUtils and the ignoreMissing field exist as shown in the PR and NetworkDirectionUtils.isInternal may throw IllegalArgumentException for invalid inputs. Given these, the change is safe and executable and does not introduce new bugs.

Comment on lines +39 to +40
for (String network : networks) {
if (inNetwork(InetAddresses.forString(ip), network)) {
Copy link

Choose a reason for hiding this comment

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

Suggestion: Validate inputs and guard against null or invalid ip in isInternal to avoid exceptions when InetAddresses.forString(ip) is called and to handle a null/empty networks list. [possible issue]

Suggested change
for (String network : networks) {
if (inNetwork(InetAddresses.forString(ip), network)) {
if (networks == null || networks.isEmpty() || ip == null) {
return false;
}
InetAddress address;
try {
address = InetAddresses.forString(ip);
} catch (IllegalArgumentException e) {
// invalid IP string -> treat as not internal
return false;
}
for (String network : networks) {
if (inNetwork(address, network)) {
Why Change? ⭐

The improved code adds defensive checks for null/empty networks and a null ip, ensuring we don't iterate null lists or pass null into InetAddresses.forString.
It also wraps parsing in a try/catch for IllegalArgumentException thrown by InetAddresses.forString, returning false for invalid IP strings rather than propagating an exception.
All referenced symbols (InetAddresses, InetAddress, inNetwork) are already present/used elsewhere in this file, so compilation is not impacted.
The control flow and return semantics remain consistent: if any network contains the parsed address we return true, otherwise false. This avoids potential runtime NPEs/IllegalArgumentException without introducing new dependencies or changing the method's outward contract except to be more tolerant of null/invalid inputs.

private void testNetworkDirectionUtils(String source, String destination, List<String> networks, String expectedDirection) {
boolean sourceInternal = NetworkDirectionUtils.isInternal(networks, source);
boolean destinationInternal = NetworkDirectionUtils.isInternal(networks, destination);
assertThat(expectedDirection, equalTo(NetworkDirectionUtils.getDirection(sourceInternal, destinationInternal)));
Copy link

Choose a reason for hiding this comment

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

Suggestion: Use the computed direction as the "actual" argument to assertThat and keep the expected value as the matcher to produce clearer failure messages (i.e., swap the current assertThat arguments). [best practice]

Suggested change
assertThat(expectedDirection, equalTo(NetworkDirectionUtils.getDirection(sourceInternal, destinationInternal)));
assertThat(NetworkDirectionUtils.getDirection(sourceInternal, destinationInternal), equalTo(expectedDirection));
Why Change? ⭐

The improved code only swaps the arguments to assertThat so the actual value (the result of NetworkDirectionUtils.getDirection(...)) is passed first and the matcher equalTo(expectedDirection) second. This matches the standard Hamcrest assertThat(actual, matcher) ordering, yields clearer failure messages, and does not change any logic. Types align (both are String), the used methods and imports already exist in the file (NetworkDirectionUtils and equalTo), and no new APIs or context are required. The change is syntactic and executable and will not introduce runtime errors.

@@ -0,0 +1,5 @@
pr: 136133
summary: Implement `network_direction` function
area: ES|QL
Copy link

Choose a reason for hiding this comment

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

Suggestion: Quote the area value (which contains a pipe character) to avoid YAML parsing ambiguity and ensure consistent cross-platform parsing. [best practice]

Suggested change
area: ES|QL
area: "ES|QL"
Why Change? ⭐

Quoting the 'area' scalar is a harmless, backward-compatible change that prevents any YAML parsers
or tooling from misinterpreting the '|' character. The improved code preserves the exact string value
("ES|QL") while ensuring the YAML remains unambiguous across different parsers and platforms.
The syntax is valid YAML, no new identifiers or methods are referenced, and the document remains
structurally identical aside from the added quotes. Therefore this change is executable and safe.

**Description**

Returns the direction type (inbound, outbound, internal, external) given a source IP address, destination IP address, and a list of internal networks.

Copy link

Choose a reason for hiding this comment

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

Suggestion: Enrich the snippet with parameter descriptions and a short usage example to make the function purpose and input expectations clearer for readers. [enhancement]

Suggested change
**Parameters**
- `src_ip` (string): source IP address.
- `dst_ip` (string): destination IP address.
- `internal_networks` (array<string>): list of CIDR ranges considered internal (e.g. `"10.0.0.0/8"`).
**Example**
```esql
network_direction("10.0.0.5", "8.8.8.8", ["10.0.0.0/8"]) -- returns "outbound"
<details>
<summary><b>Why Change? ⭐ </b></summary>

This suggestion only augments a markdown snippet with additional documentation (parameters and an example). It does not alter any executable code or references to programmatic APIs, so there is no risk of introducing runtime bugs.

The improved content uses standard markdown and a fenced code block labelled `esql`, which is consistent with documentation formatting and fully comprehensible from the PR diff. It does not reference any undefined variables, methods, or types from the codebase.

Assumptions:
- The file is a documentation snippet and not executed during runtime.
- The described function name (`network_direction`) and parameter semantics match the implementation referenced by the snippet (the change is purely descriptive).

Given these points, the change is safe, executable as documentation, and improves clarity without affecting production code.
</details>

Comment on lines +5 to +8
| source_ip | destination_ip | internal_networks | result |
| --- | --- | --- | --- |
| ip | ip | keyword | keyword |
| ip | ip | text | keyword |
Copy link

Choose a reason for hiding this comment

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

Suggestion: Use inline code formatting for column names and type entries (e.g., source_ip, keyword) to improve readability and consistency in API/type docs. [best practice]

Suggested change
| source_ip | destination_ip | internal_networks | result |
| --- | --- | --- | --- |
| ip | ip | keyword | keyword |
| ip | ip | text | keyword |
| `source_ip` | `destination_ip` | `internal_networks` | `result` |
| --- | --- | --- | --- |
| `ip` | `ip` | `keyword` | `keyword` |
| `ip` | `ip` | `text` | `keyword` |
Why Change? ⭐

This suggestion updates only the Markdown formatting of the table cells by surrounding names and types with
inline code backticks. It is purely a documentation styling change; it does not alter any program logic or data.
Markdown supports inline code using backticks, so rendering will be unaffected and no runtime errors can be introduced.
Assumes the file is consumed as Markdown documentation (consistent with the file path and extension).

},
{
"name" : "internal_networks",
"type" : "keyword",
Copy link

Choose a reason for hiding this comment

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

Suggestion: Treat internal_networks as an array type in the first signature (e.g., keyword[]) since the description says it's a list of networks. [possible issue]

Suggested change
"type" : "keyword",
"type" : "keyword[]",
Why Change? ⭐

The change is a JSON-only documentation/schema update: replacing the type string "keyword" with "keyword[]"
makes the intent (a list/array of keywords) explicit and matches the parameter description that states
"List of internal networks". This is syntactically valid JSON and does not introduce executable code changes.
Assumptions:

  • The file is a declarative function definition (documentation/metadata) and is allowed to use array notation
    in the "type" field.
  • No other parts of the PR require the type string to be exactly "keyword" (if they do, those parts would need
    coordinated updates). Given only the diff for this JSON file, the change is safe and consistent.

@refacto-visz
Copy link

refacto-visz bot commented Oct 16, 2025

Code Review: Network Direction Function Implementation

👍 Well Done
Comprehensive Test Coverage

Extensive unit tests and CSV spec examples ensure reliability across multiple network scenarios

Code Reuse Architecture

Centralized NetworkDirectionUtils eliminates duplication between ESQL and ingest processor implementations

📁 Selected files for review (22)
  • docs/changelog/136133.yaml
  • docs/reference/query-languages/esql/_snippets/functions/description/network_direction.md
  • modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/NetworkDirectionProcessorTests.java
  • server/src/main/java/org/elasticsearch/common/network/NetworkDirectionUtils.java
  • server/src/test/java/org/elasticsearch/common/network/NetworkDirectionUtilsTests.java
  • x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/argument/BlockArgument.java
  • x-pack/plugin/esql/qa/testFixtures/src/main/resources/ip.csv-spec
  • x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirectionEvaluator.java
  • x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
  • x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java
  • x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ScalarFunctionWritables.java
  • x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirection.java
  • docs/reference/query-languages/esql/_snippets/functions/examples/network_direction.md
  • x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirectionErrorTests.java
  • x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirectionSerializationTests.java
  • x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirectionTests.java
  • docs/reference/query-languages/esql/_snippets/functions/layout/network_direction.md
  • docs/reference/query-languages/esql/_snippets/functions/parameters/network_direction.md
  • docs/reference/query-languages/esql/_snippets/functions/types/network_direction.md
  • docs/reference/query-languages/esql/kibana/definition/functions/network_direction.json
  • docs/reference/query-languages/esql/kibana/docs/functions/network_direction.md
  • modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/NetworkDirectionProcessor.java
🎯 Custom Instructions
✅ Applied Instructions
Organization Guidelines
  • Keep pull requests small and focused (prefer < 400 lines of code).
  • All CI/CD checks, linting, and unit tests must pass before merge.
  • Use feature flags for new functionality and include a clear rollback plan.
  • Follow the company security checklist:
    • No hard-coded secrets or credentials.
    • Validate all external inputs.
    • Use parameterized queries for DB access.

Scope: All files

📝 Additional Comments
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/NetworkDirection.java (4)
String Conversion Redundancy

String conversion via utf8ToString() occurs twice per network entry during dual loop processing. Each conversion creates new String objects causing additional memory allocation and GC pressure. Caching converted strings would reduce allocation overhead.

Standards:

  • ISO-IEC-25010-Performance-Efficiency-Resource-Utilization
  • Memory-Allocation-Optimization
Exception Handling Enhancement

InetAddressPoint.decode operations lack explicit exception handling for malformed IP data. Uncaught exceptions during IP decoding propagate to caller causing evaluation failure. Enhanced error boundaries would improve function reliability under invalid input conditions.

Standards:

  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • SRE-Error-Handling
  • DbC-Preconditions
Exception Information Disclosure

Exception details exposed through warning system could leak sensitive information about internal network structure or IP validation logic. Attackers could use exception messages to fingerprint the system or understand internal network topology. Exception handling should sanitize error messages before exposure.

Standards:

  • CWE-209
  • OWASP-A09
  • NIST-SSDF-RV.1
Long Parameter List

Constructor accepts four parameters including verbose annotation objects, creating a complex parameter signature. While the parameters are related, the extensive annotation metadata increases constructor complexity. Consider using a builder pattern or parameter object to encapsulate network direction configuration and improve readability.

Standards:

  • Clean-Code-Functions
  • Design-Pattern-Builder
  • Refactoring-Introduce-Parameter-Object

Comment on lines +152 to +155
System.arraycopy(sourceIp.bytes, sourceIp.offset, scratch.bytes, 0, sourceIp.length);
InetAddress sourceIpAddress = InetAddressPoint.decode(scratch.bytes);
System.arraycopy(destinationIp.bytes, destinationIp.offset, scratch.bytes, 0, destinationIp.length);
InetAddress destinationIpAddress = InetAddressPoint.decode(scratch.bytes);
Copy link

Choose a reason for hiding this comment

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

Array Copy Risk

Array copy operations overwrite scratch buffer without bounds checking. Buffer overflow possible if IP address length exceeds scratch buffer capacity. System crash or memory corruption results from buffer overflow scenarios.

        if (sourceIp.length > scratch.bytes.length) {
            throw new IllegalArgumentException("Source IP length exceeds buffer capacity");
        }
        System.arraycopy(sourceIp.bytes, sourceIp.offset, scratch.bytes, 0, sourceIp.length);
        InetAddress sourceIpAddress = InetAddressPoint.decode(Arrays.copyOf(scratch.bytes, sourceIp.length));
        
        if (destinationIp.length > scratch.bytes.length) {
            throw new IllegalArgumentException("Destination IP length exceeds buffer capacity");
        }
        System.arraycopy(destinationIp.bytes, destinationIp.offset, scratch.bytes, 0, destinationIp.length);
        InetAddress destinationIpAddress = InetAddressPoint.decode(Arrays.copyOf(scratch.bytes, destinationIp.length));
Commitable Suggestion
Suggested change
System.arraycopy(sourceIp.bytes, sourceIp.offset, scratch.bytes, 0, sourceIp.length);
InetAddress sourceIpAddress = InetAddressPoint.decode(scratch.bytes);
System.arraycopy(destinationIp.bytes, destinationIp.offset, scratch.bytes, 0, destinationIp.length);
InetAddress destinationIpAddress = InetAddressPoint.decode(scratch.bytes);
if (sourceIp.length > scratch.bytes.length) {
throw new IllegalArgumentException("Source IP length exceeds buffer capacity");
}
System.arraycopy(sourceIp.bytes, sourceIp.offset, scratch.bytes, 0, sourceIp.length);
InetAddress sourceIpAddress = InetAddressPoint.decode(Arrays.copyOf(scratch.bytes, sourceIp.length));
if (destinationIp.length > scratch.bytes.length) {
throw new IllegalArgumentException("Destination IP length exceeds buffer capacity");
}
System.arraycopy(destinationIp.bytes, destinationIp.offset, scratch.bytes, 0, destinationIp.length);
InetAddress destinationIpAddress = InetAddressPoint.decode(Arrays.copyOf(scratch.bytes, destinationIp.length));
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • SRE-Error-Handling

Comment on lines +160 to +171
for (int i = first; i < first + valueCount; i++) {
if (NetworkDirectionUtils.inNetwork(sourceIpAddress, networks.getBytesRef(i, netScratch).utf8ToString())) {
sourceInternal = true;
break;
}
}
for (int i = first; i < first + valueCount; i++) {
if (NetworkDirectionUtils.inNetwork(destinationIpAddress, networks.getBytesRef(i, netScratch).utf8ToString())) {
destinationInternal = true;
break;
}
}
Copy link

Choose a reason for hiding this comment

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

Nested Loop Performance

Two sequential loops iterate through the same networks array, causing O(2n) complexity. Each loop processes identical network entries but for different IP addresses. Performance degrades with large network lists affecting query execution time.

        for (int i = first; i < first + valueCount; i++) {
            String network = networks.getBytesRef(i, netScratch).utf8ToString();
            if (!sourceInternal && NetworkDirectionUtils.inNetwork(sourceIpAddress, network)) {
                sourceInternal = true;
            }
            if (!destinationInternal && NetworkDirectionUtils.inNetwork(destinationIpAddress, network)) {
                destinationInternal = true;
            }
            if (sourceInternal && destinationInternal) {
                break;
            }
        }
Commitable Suggestion
Suggested change
for (int i = first; i < first + valueCount; i++) {
if (NetworkDirectionUtils.inNetwork(sourceIpAddress, networks.getBytesRef(i, netScratch).utf8ToString())) {
sourceInternal = true;
break;
}
}
for (int i = first; i < first + valueCount; i++) {
if (NetworkDirectionUtils.inNetwork(destinationIpAddress, networks.getBytesRef(i, netScratch).utf8ToString())) {
destinationInternal = true;
break;
}
}
for (int i = first; i < first + valueCount; i++) {
String network = networks.getBytesRef(i, netScratch).utf8ToString();
if (!sourceInternal && NetworkDirectionUtils.inNetwork(sourceIpAddress, network)) {
sourceInternal = true;
}
if (!destinationInternal && NetworkDirectionUtils.inNetwork(destinationIpAddress, network)) {
destinationInternal = true;
}
if (sourceInternal && destinationInternal) {
break;
}
}
Standards
  • ISO-IEC-25010-Performance-Efficiency-Time-Behavior
  • Algorithmic-Complexity-Linear-Optimization

Comment on lines +38 to +45
public static boolean isInternal(List<String> networks, String ip) {
for (String network : networks) {
if (inNetwork(InetAddresses.forString(ip), network)) {
return true;
}
}
return false;
}
Copy link

Choose a reason for hiding this comment

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

Missing Input Validation

Method accepts null parameters without validation, potentially causing NullPointerException when networks list or ip string is null. InetAddresses.forString() will throw IllegalArgumentException for invalid IP strings. Business logic requires defensive validation to prevent runtime failures and ensure predictable network direction classification.

    public static boolean isInternal(List<String> networks, String ip) {
        if (networks == null || ip == null || networks.isEmpty()) {
            return false;
        }
        try {
            InetAddress address = InetAddresses.forString(ip);
            for (String network : networks) {
                if (network != null && inNetwork(address, network)) {
                    return true;
                }
            }
        } catch (IllegalArgumentException e) {
            return false;
        }
        return false;
    }
Commitable Suggestion
Suggested change
public static boolean isInternal(List<String> networks, String ip) {
for (String network : networks) {
if (inNetwork(InetAddresses.forString(ip), network)) {
return true;
}
}
return false;
}
public static boolean isInternal(List<String> networks, String ip) {
if (networks == null || ip == null || networks.isEmpty()) {
return false;
}
try {
InetAddress address = InetAddresses.forString(ip);
for (String network : networks) {
if (network != null && inNetwork(address, network)) {
return true;
}
}
} catch (IllegalArgumentException e) {
return false;
}
return false;
}
Standards
  • Business-Rule-Input-Validation
  • Logic-Verification-Null-Safety
  • Algorithm-Correctness-Error-Handling

if (inNetwork(InetAddresses.forString(ip), network)) {
Copy link

Choose a reason for hiding this comment

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

IP Address Injection Risk

Direct string-to-IP conversion without validation allows malformed IP strings to cause exceptions or unexpected behavior. Attackers could inject malicious IP formats to trigger parsing errors or bypass network classification logic. Input validation should occur before IP address parsing to prevent injection attacks.

Standards
  • CWE-20
  • OWASP-A03
  • NIST-SSDF-PW.1

default -> CIDRUtils.isInRange(NetworkAddress.format(address), network);
Copy link

Choose a reason for hiding this comment

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

Network String Injection Vulnerability

Unvalidated network string passed directly to CIDR range checking allows injection of malicious network patterns. Attackers could inject crafted network strings to bypass security classifications or cause parsing exceptions. Network string validation should occur before CIDR processing to prevent injection attacks.

            default -> {
                if (network == null || network.trim().isEmpty()) {
                    yield false;
                }
                try {
                    yield CIDRUtils.isInRange(NetworkAddress.format(address), network.trim());
                } catch (IllegalArgumentException e) {
                    yield false;
                }
            }
Commitable Suggestion
Suggested change
default -> CIDRUtils.isInRange(NetworkAddress.format(address), network);
default -> {
if (network == null || network.trim().isEmpty()) {
yield false;
}
try {
yield CIDRUtils.isInRange(NetworkAddress.format(address), network.trim());
} catch (IllegalArgumentException e) {
yield false;
}
}
Standards
  • CWE-20
  • OWASP-A03
  • NIST-SSDF-PW.1

Comment on lines +135 to +167
@Evaluator(warnExceptions = IllegalArgumentException.class)
static void process(
BytesRefBlock.Builder builder,
@Fixed(includeInToString = false, scope = THREAD_LOCAL) BytesRef scratch,
@Fixed(includeInToString = false, scope = THREAD_LOCAL) BytesRef netScratch,
BytesRef sourceIp,
BytesRef destinationIp,
@Position int position,
BytesRefBlock networks
) {
int valueCount = networks.getValueCount(position);
if (valueCount == 0) {
builder.appendNull();
return;
}
int first = networks.getFirstValueIndex(position);

System.arraycopy(sourceIp.bytes, sourceIp.offset, scratch.bytes, 0, sourceIp.length);
InetAddress sourceIpAddress = InetAddressPoint.decode(scratch.bytes);
System.arraycopy(destinationIp.bytes, destinationIp.offset, scratch.bytes, 0, destinationIp.length);
InetAddress destinationIpAddress = InetAddressPoint.decode(scratch.bytes);

boolean sourceInternal = false;
boolean destinationInternal = false;

for (int i = first; i < first + valueCount; i++) {
if (NetworkDirectionUtils.inNetwork(sourceIpAddress, networks.getBytesRef(i, netScratch).utf8ToString())) {
sourceInternal = true;
break;
}
}
for (int i = first; i < first + valueCount; i++) {
if (NetworkDirectionUtils.inNetwork(destinationIpAddress, networks.getBytesRef(i, netScratch).utf8ToString())) {
Copy link

Choose a reason for hiding this comment

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

Missing Abstraction Layer

Complex low-level byte manipulation and network processing logic embedded directly in evaluator method. This creates tight coupling between ESQL evaluation framework and network analysis implementation. The method handles byte array copying, IP address decoding, and network iteration logic that should be abstracted into separate service classes.

Standards
  • SOLID-SRP
  • Clean-Code-Functions
  • Design-Pattern-Strategy

@codeant-ai
Copy link

codeant-ai bot commented Oct 16, 2025

Pull Request Feedback 🔍

🔒 No security issues identified
⚡ Recommended areas for review

  • Missing input validation
    Several public methods (for example isInternal, inNetwork, and isLocalOrPrivate) assume non-null, parseable inputs. If networks or network entries are null/empty, or if ip is null, callers will get NPEs or uncaught exceptions. The code should defensively validate and handle null/empty inputs to avoid runtime crashes or surprising behavior.

  • Unhandled parse exceptions
    The code calls InetAddresses.forString(ip) (and likely CIDRUtils.isInRange(...)) without try/catch. Invalid IP strings or malformed CIDR/network inputs will throw IllegalArgumentException (or similar) and propagate. The PR description indicates invalid inputs should produce warnings and null results — that behavior is not implemented here; consider catching parse/validation errors and returning a safe value or bubbling a controlled response.

  • Constructor/Reflection
    The registry uses reflection to build function descriptions and to locate constructors (see constructorFor/description). Ensure the new NetworkDirection class exposes the expected public constructor(s) (matching the ternary builder signature used here) and/or the @FunctionInfo annotation so descriptor and implicit-casting logic works correctly. If the constructor signature doesn't match, parsing/description logic may behave unexpectedly or throw at runtime.

  • Null inputs
    The evaluator does not check for null sourceIp or destinationIp BytesRef values. If either argument is null an immediate NPE will occur (or decode can behave unexpectedly). The function should produce a null result for missing inputs rather than throwing.

  • Null entrants in networks
    When internalNetworks are rendered per-document via d.renderTemplate(...), that method could return null for some templates. Passing a list with null elements into NetworkDirectionUtils.isInternal(...) could cause unexpected behavior or NPEs depending on the utils implementation. Consider filtering out nulls and validating entries before calling the shared utility.

  • Empty networks handling
    The code builds a networks list and then calls NetworkDirectionUtils.isInternal(...) without explicitly handling the case where networks is empty or contains only invalid/null entries. The PR description says the function should return null when the networks list is empty, but the current processor will proceed and may classify traffic as external instead. Confirm intended behavior and add an explicit empty-list check if the desired result is null.

entries.add(Locate.ENTRY);
entries.add(Log.ENTRY);
entries.add(Md5.ENTRY);
entries.add(NetworkDirection.ENTRY);
Copy link

Choose a reason for hiding this comment

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

Suggestion: Ensure NetworkDirection exposes a public static ENTRY of the correct type before registering it — either add the ENTRY constant and reader/writer to NetworkDirection, or guard the registry call (e.g. check for the ENTRY field via reflection and only register it if present and of type NamedWriteableRegistry.Entry) to avoid runtime initialization failures. [possible issue]

Comment on lines +152 to +155
System.arraycopy(sourceIp.bytes, sourceIp.offset, scratch.bytes, 0, sourceIp.length);
InetAddress sourceIpAddress = InetAddressPoint.decode(scratch.bytes);
System.arraycopy(destinationIp.bytes, destinationIp.offset, scratch.bytes, 0, destinationIp.length);
InetAddress destinationIpAddress = InetAddressPoint.decode(scratch.bytes);
Copy link

Choose a reason for hiding this comment

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

Suggestion: Ensure the InetAddressPoint.decode call receives a byte array whose length exactly matches the IP length by copying only the bytes for the IP (use Arrays.copyOfRange or similar) instead of passing the entire 16-byte scratch buffer. [possible bug]

@codeant-ai
Copy link

codeant-ai bot commented Oct 16, 2025

CodeAnt AI finished reviewing your PR.

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

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants