HydraRoute Documentation

repository·main·Indexed 19 days ago

https://github.com/ground-zerro/hydraroute

A routing tool for Keenetic routers that enables split tunneling by directing specific domain and CIDR traffic through tunnels while maintaining direct routing for other traffic. Available in Classic and Neo versions, it integrates with AdGuard Home for DNS-based routing and supports L7 interception (TLS SNI/HTTP Host) to handle encrypted DNS and hardcoded IPs. Features include support for GeoIP/GeoSite, custom access policies, and a Web UI for domain management.

Tokens
27.4K
Snippets
63
Records
110
Agent score
64%

What's inside HydraRoute

  1. Overview of HRNeo v3.17.0-1

    main

    HRNeo (v3.17.0-1) is a compact, single-threaded policy routing daemon written in pure C, specifically designed for Keenetic routers. It functions by intercepting hostnames and routing traffic based on identified IP addresses.

    Core Functionality

    HRNeo uses two primary channels to identify hostnames:

    1. DNS Channel: Intercepts DNS responses using AF_PACKET SOCK_DGRAM combined with L3-BPF filters in the kernel. This allows it to work across various interface types (Ethernet, PPP, tunnels, etc.) to capture DNS traffic from LAN and VPN clients.
    2. L7 Channel: When l7CaptureEnabled is active, it uses a custom NFLOG client via raw netlink to perform passive copying of outgoing connections. It identifies hosts via TLS SNI, HTTP Host, or QUIC Initial SNI. For fragmented packets, it performs TCP reassembly or QUIC CRYPTO-walking (including HKDF + AES-128-CTR decryption) to extract host information.

    Routing Mechanism

    Once an IP address is extracted, HRNeo:

    • Adds the IP to an ipset via netlink.
    • Marks traffic in iptables/mangle for policy routing.
    • Supports routing via Keenetic policies (using marks via the RCI API) or direct interface routing (fwmark + ip rule + ip route).
    • Supports GeoIP/GeoSite data in .dat (v2ray/xray) format using streaming protobuf parsing.
  2. What is HydraRoute

    main
    HydraRoute is a tool for split traffic routing based on domains and CIDR blocks using tunnels on Keenetic routers. It allows you to route traffic destined for specific domains or CIDR ranges through a tunnel, while all other traffic is sent directly through your standard connection.
  3. Configure L7 HTTP/HTTPS interception

    main

    L7 interception allows HydraRoute to monitor TLS SNI and HTTP Host headers to close 'blind spots' left by DNS-only monitoring (such as DoH, DoT, DoQ, hardcoded IPs, and QUIC/HTTP-3).

    Key behaviors:

    • It uses NFLOG to copy packets, making it compatible with DPI desynchronizers (like zapret2, nfqws2, or tpws) that use NFQUEUE.
    • If ConntrackFlush=true is set, HydraRoute will surgically remove the specific connection (by 5-tuple) that matches a watchlist entry, forcing a reconnect through the routing policy.
    • If ConntrackFlush=false, the connection remains unmanaged until the application reconnects.
    • Note: L7 interception does not support MITM; DoH/DoT/DoQ/ECH decryption is not possible.
    # Example L7 configuration
    l7CaptureEnabled=true
    l7EnableTLS=true
    l7EnableHTTP=true
    l7WanInterface=eth3
    l7NflogGroup=210
    ConntrackFlush=true
  4. L7 Connection Reconnect via Conntrack Deletion

    main

    To ensure that a connection identified via L7 (which often occurs after the TCP connection is already established) is actually routed through the intended policy, HydraRoute can perform a targeted connection deletion.

    Mechanism:

    1. When a new IP is added to the ipset (detected via NLM_F_EXCL returning a new entry).
    2. And if ConntrackFlush=true is configured.
    3. HydraRoute calls conntrack_delete_conn(proto=TCP/UDP) to perform a precise 5-tuple DELETE.

    This forces the client to reconnect. On the next attempt, the kernel's CONNMARK rules will evaluate the new connection against the updated policy, ensuring the traffic follows the correct route. This is more reliable than sending spoofed RST packets, which often fail due to strict sequence number checks (RFC 5961).

  5. Configure Policy-Based Routing via CONNMARK rules

    main

    HydraRoute implements policy-based routing using iptables/ip6tables and CONNMARK. This mechanism ensures that once a connection is marked by a policy or interface, it stays on that route for the duration of the session, preventing mid-session re-routing.

    CONNMARK Rule Logic

    When GlobalRouting=false, the system uses a guard condition to ensure the 'first match wins'. The rule checks if a connection has not yet been marked (! --mark 0xffffaa0/0xffffff0) before applying a new mark.

    Rule Templates:

    Marking a new connection: Matches an IP in a specific ipset and sets the CONNMARK.

    -A PREROUTING -m mark ! --mark 0xffffaa0/0xffffff0
       -m connmark --mark 0x0/0xffffffff
       -m set --match-set <ipset> dst
       -j CONNMARK --set-xmark 0x<mark>/0xffffffff

    Restoring mark for existing connections: Restores the mark from the connection tracking state to the packet.

    -A PREROUTING -m set --match-set <ipset> dst
       -j CONNMARK --restore-mark --nfmask 0xffffffff --ctmask 0xffffffff

    Global Routing Mode: If GlobalRouting=true, the guard condition ! --mark 0xffffaa0/0xffffff0 is removed, allowing subsequent rules to potentially overwrite marks.

    # Example of the marking rule structure
    -A PREROUTING -m mark ! --mark 0xffffaa0/0xffffff0
       -m connmark --mark 0x0/0xffffffff
       -m set --match-set <ipset> dst
       -j CONNMARK --set-xmark 0x<mark>/0xffffffff
  6. Domain Matching and CNAME Chain Traversal

    main

    HydraRoute uses a multi-layered approach to match domains to policies.

    match_domain Logic

    1. Exact Match: Checks via hash table lookup.
    2. Suffix Match: Checks parent domains (e.g., sub.example.com matches example.com).
    3. Priority & Specificity:
      • Priority is determined by the policy_order (lower index = higher priority).
      • If priorities are equal, the most specific match wins (longer suffix length; exact matches have specificity = domain_len + 1).

    CNAME Chain Traversal

    match_domain_with_cname performs a Breadth-First Search (BFS) through CNAME records to find a matching ipset_name.

    • Depth: Up to MAX_CNAME_CHAIN=16 steps.
    • Bidirectional: Searches both forward (from == current) and backward (to == current).
    • Safety: Uses visited_hashes (FNV-1a) to prevent infinite loops in circular CNAME chains.
  7. Enable L7 interception (TLS SNI / HTTP Host)

    main

    L7 interception can be enabled by setting l7CaptureEnabled=true. This mechanism runs in parallel with the DNS channel to capture hostnames, closing visibility gaps left by DNS-only schemes.

    It addresses:

    • Encrypted DNS (DoH/DoT/DoQ)
    • Hardcoded IPs using TLS SNI
    • Legacy HTTP
    • Device DNS caches with unexpired TTL
    • QUIC / HTTP/3 (UDP/443)

    How it works: L7 sees the hostname after the connection is established. When Neo adds an IP to an ipset, it surgically removes the conntrack entry for the triggering connection via its 5-tuple. This forces the browser or application to re-establish the connection, which then follows the correct policy from the start.

    Limitations:

    • ECH (Encrypted ClientHello): Cannot be intercepted without MITM.
    • iCloud Private Relay: Designed as an encrypted tunnel.
    l7CaptureEnabled=true
  8. How hostname events and conntrack flushing work

    main

    When a hostname is resolved, HydraRoute triggers a hostname event to update IP sets and manage connection tracking.

    process_hostname_event (DNS Channel)

    Used when a DNS response provides new IPs.

    • It matches the domain (including CNAME chains) against configured ipsets.
    • It adds IPv4 and IPv6 batches to the corresponding sets.
    • Conntrack Flushing: If allow_conntrack_flush=1 and new IPs are detected, it requests an asynchronous flush. The conntrack_flush_request() puts new IPs into a pending buffer; the actual DUMP+DELETE occurs asynchronously in the epoll cycle via conntrack_process to avoid blocking DNS processing.

    process_hostname_event_l7 (L7 Channel)

    Used when an established connection is intercepted (e.g., via TLS-SNI or HTTP-Host).

    • It receives a l7_conn_t containing the 5-tuple (client/server IPs and ports).
    • Instead of a full table dump, it performs a targeted conntrack_delete_conn. This deletes only the specific connection that triggered the event, forcing a reconnect to ensure the new routing policy is applied. This is an $O(1)$ operation.
  9. Resolve target collisions with PolicyOrder

    main

    When a single address (domain or IP) matches multiple routing targets—for example, a domain in domain.conf pointing to a provider interface while its IP falls into a broad CIDR block in another target—the PolicyOrder setting determines the winner.

    PolicyOrder treats Keenetic policies and DirectRoute interfaces as equals. They are listed together in the configuration, and the first target in the list takes precedence for both IP and domain collisions (including CNAME chains).

    Key behaviors:

    • Precedence: The target listed earlier in PolicyOrder wins. Targets not explicitly listed in PolicyOrder are processed in alphabetical order.
    • Global Routing: By default (GlobalRouting=false), if a device is assigned to a policy within KeeneticOS settings, KeeneticOS marks the traffic before hrneo can, and hrneo will yield to that device. To force hrneo rules on such devices, set GlobalRouting=true.
    • Connection Marking: A target is fixed to a connection upon the first packet. To force existing connections to switch to a new route when a target changes, set ConntrackFlush=true.
    PolicyOrder=eth3,nwg1
  10. How DNS detection works via AF_PACKET capture

    main

    HRNeo uses AF_PACKET sockets with SOCK_DGRAM to capture DNS responses. This method is chosen over SOCK_RAW because SOCK_DGRAM allows the kernel to strip the Layer 2 (Ethernet) header, providing a consistent IP-level packet regardless of the interface type (Ethernet, PPP, WireGuard, etc.). This ensures DNS responses are correctly captured for non-Ethernet interfaces like VPN tunnels.

    Key technical details:

    • Socket Type: AF_PACKET with SOCK_DGRAM and ETH_P_ALL. ETH_P_ALL is required to intercept outgoing packets from physical interfaces (like dnsmasq responses).
    • BPF Filters: Uses L3 BPF filters to isolate DNS traffic (UDP/TCP port 53) for both IPv4 and IPv6.
    • Buffer: Uses a SO_RCVBUF of 1 MB (SOCKET_READ_BUFFER).
  11. Error Handling and Resilience in RCI Operations

    main

    HRNeo is designed to be resilient to RCI failures or delays.

    Policy Mark Assignment Latency: When a policy is newly created, the router might not assign a markID immediately. If rci_get_policy_mark returns 0 (policy exists but no mark assigned), HRNeo does not crash or stop. Instead:

    1. It logs a warning: LOG_WARN "Policy %s has no mark ID yet".
    2. It skips that specific target in the current batch.
    3. The commit_run() function in main.c implements an exponential backoff (1s, 2s, 4s... up to 60s) to retry the application of rules.

    Degraded Mode: If the router is unreachable, the HRNeo daemon continues to run in a degraded mode. It will continue to populate ipset via DNS/L7 channels, even if specific policies temporarily lack CONNMARK rules due to RCI communication issues.

  12. Limits and memory usage for GeoSite (geo.dat)

    main

    GeoSite data is loaded into the same domain_hashtable_t as domain.conf, meaning they share the same memory budget.

    Per-tag Memory Consumption

    During the build_geosite_domain_map process, peak memory for a single tag is approximately N domains × ~50 bytes (for the initial array) plus the main hashtable chunks.

    Tag SizePeak RAM
    10,000 domains~1 MB
    100,000 domains~10 MB
    500,000 domains~50 MB
    1,000,000 domains~100 MB (Risky on 128 MB routers)

    Hard Limits for GeoSite

    • Max GeoSite Files: 16 (MAX_GEO_FILES via GeoSiteFile=)
    • Max geosite rules: 256 geosite:TAG/Policy entries in domain.conf (gs_rules[256])
    • Tag name length: ≤ 63 bytes (MAX_TAG_LEN - 1)
    • Supported domain types: Domain and Full. Plain and Regex types are skipped with a [WARN] message.
    • File size: The .dat file size itself is unlimited due to streaming reads.