Update on ICAP integration proposal: Working implementation of SSLproxy (icap branch) & icapsuricata (libsuricata service)

Hi All,

Following up on my ICAP integration proposal from earlier this year—specifically the architectural shift discussed in this topic on the Suricata forum—I wanted to share a major update regarding working software implementations for both components.

Over the last few months, I have moved this from a theoretical proposal to working prototypes proving the validity of the ICAP model for inline decryption and deep inspection.

Here is the current status:

  1. SSLproxy (icap branch): The icap branch of SSLproxy now features a full, functional ICAP client implementation. It has been validated against standard ecosystem services including c-icap (echo/ex206), squidclamav, E2Guardian, and my newly developed custom Suricata service plugin.
  2. icapsuricata (c-icap service module): To bridge the gap between the proxy layer and the engine, I have launched icapsuricata. This is a custom c-icap service module running Suricata in library mode (libsuricata). Rather than passive interface capture, it interacts inline directly with the proxy stream. The module processes c-icap data blocks, dynamically constructs sequentially aligned, in-memory TCP/IP packet streams, and injects them straight into libsuricata for real-time app-layer tracking and signature detection. To ensure high performance and zero heap memory fragmentation, it utilizes a custom single-writer, dual-reader circular ring buffer.

Please note that both codebases are actively in a Work-In-Progress (WIP) phase and are intended as architectural proofs-of-concept rather than production-ready artifacts. However, they serve as a concrete, working implementation of the proposed ICAP paradigm.

I would highly value your technical feedback, comments, or suggestions on this approach.

Best,
Soner

Very cool @Soner_Tari :slight_smile:

This should also help us more properly define a public libsuricata API. Are there any clear pain points in the libsuricata handling so far?

Thanks Victor for the reply.

Overall, my experience developing icapsuricata against libsuricata has been very positive. The integration was smooth, and the library mode behaves predictably. That said, since this is a fresh proof-of-concept and hasn’t faced high-throughput production stress yet, my feedback should be taken with a grain of salt.

To answer your question regarding pain points or structural hurdles, the most notable observation relates to data ingestion styles:

The Packet Emulation Requirement vs. Stream Ingestion
When I first approached libsuricata for purely inline content inspection (rather than L3/L4 network anomaly detection), I was initially hoping I could bypass low-level packet emulation entirely and feed raw application-layer proxy stream chunks straight into the engine.

However, because Suricata’s stream reassembly, protocol parsing, and detection matrices are tightly coupled to the state transitions of the TCP engine, packet emulation turned out to be strictly necessary. To keep StreamTcp aligned, I have to synthesize IPv4/TCP headers and manually manage sequence/acknowledgment tracking spaces.

Furthermore, I discovered that to prevent app-layer blindspots (due to internal optimizations like skipping frame inspection on non-state-changing payload updates), I have to systematically inject synthetic cross-direction ACK packets mid-stream (currently 4KB chunks) to act as evaluation/flushing triggers, alongside a final sequence-incremented FIN|ACK sweep to cleanly finalize the flow.

While this requires some emulation overhead, I realized it is an inherent requirement of the current architecture. It ensures that Suricata can perfectly handle stream reassembly, especially when signature payloads (like an HTTP response body match) happen to cross the boundaries of multiple contiguous ICAP chunks.

If a future public libsuricata API eventually exposes a purely stream-oriented or transaction-oriented ingestion interface (bypassing the need to fake a wire tap), it would be a massive win for proxy integrations. But the current packet-level injection is absolutely viable.

As a quick side note regarding configuration management: I noticed the ongoing forum discussions about libsuricata config handling, but I haven’t yet implemented or tested live-reloading the Yaml configuration or dynamically updating IDS rules from within icapsuricata. For now, it’s a static load at initialization, so I don’t have any feedback on that front just yet.

Next Steps & Deep Testing Challenges
The next items on my roadmap will provide a much more thorough test of libsuricata’s boundary limits:

  • Ecosystem Integration: I am currently integrating the SSLproxy + icapsuricata combination into my UTM firewall project (UTMFW) for extensive, multi-client HTTP/1 verification.
  • Service Chaining: I plan to test SSLproxy running multiple concurrent ICAP services in series (such as passing traffic through E2Guardian and icapsuricata sequentially).
  • The HTTP/2 and HTTP/3 Frontier: This will be the real trial. Extending packet emulation and stream tracking to accommodate HTTP/2 multiplexed streams and HTTP/3 QUIC frames within an asynchronous ICAP loop is going to be complex. Seeing how libsuricata’s app-layer parsers coordinate with emulated network frames under heavy stream multiplexing will provide excellent data for refining the public API.
  • Protocol Expansion: Down the road, I want to expand this past HTTP to see how other proxy-handled protocols adapt to this model.

I’d like to hear your thoughts on how the current engine expects stream flushes, or if there are cleaner ways to signal transaction progress through libsuricata without faking many ACKs!

Quick reply for now: the ACK flushes should only be needed in the (default) IDS mode of stream operations. I think for this usecase the stream engine should be put in IPS/inline mode. In that case segments are processed immediately w/o a wait for an ACK.

See


bool StreamTcpInlineMode(void)
{   
    return (stream_config.flags & STREAMTCP_INIT_FLAG_INLINE); 
}           

Some other places may use EngineModeIsIPS.

First API issue with libsuricata: When including <suricata/stream-tcp.h> (to call StreamTcpInlineMode()) alongside standard system networking headers (or frameworks like c-icap that pull them in implicitly), the compiler throws hard redeclaration errors.

Specifically, the TcpState enum in stream-tcp-private.h redefines globally scoped symbols that clash directly with standard definitions in <netinet/tcp.h>:

/* Conflicts with /usr/include/netinet/tcp.h */
enum TcpState {
    TCP_NONE = 0,
    // TCP_LISTEN = 1,
    TCP_SYN_SENT = 2,
    TCP_SYN_RECV = 3,
    TCP_ESTABLISHED = 4,
    ...
};

Because these are exposed in the flat namespace, it prevents external proxy or server applications from compiling cleanly if they manage network sockets directly.

As a temporary workaround in icapsuricata, I’ve managed to bypass this by sandboxing the header inclusion with preprocessor macro masking right before pulling in the Suricata headers:

// Temporarily rename conflicting symbols to protect them from netinet/tcp.h
#define TCP_SYN_SENT    SURI_TCP_SYN_SENT
#define TCP_SYN_RECV    SURI_TCP_SYN_RECV
#define TCP_ESTABLISHED SURI_TCP_ESTABLISHED
#define TCP_FIN_WAIT1   SURI_TCP_FIN_WAIT1
#define TCP_FIN_WAIT2   SURI_TCP_FIN_WAIT2
#define TCP_TIME_WAIT   SURI_TCP_TIME_WAIT
#define TCP_LAST_ACK    SURI_TCP_LAST_ACK
#define TCP_CLOSE_WAIT  SURI_TCP_CLOSE_WAIT
#define TCP_CLOSING     SURI_TCP_CLOSING
#define TCP_CLOSED      SURI_TCP_CLOSED

#include <suricata/stream-tcp.h>

// Restore standard definitions for the rest of the codebase, not used in icapsuricata
#undef TCP_SYN_SENT
#undef TCP_SYN_RECV
#undef TCP_ESTABLISHED
#undef TCP_FIN_WAIT1
#undef TCP_FIN_WAIT2
#undef TCP_TIME_WAIT
#undef TCP_LAST_ACK
#undef TCP_CLOSE_WAIT
#undef TCP_CLOSING
#undef TCP_CLOSED

Moving forward with a public libsuricata API definition, it would be amazing if internal state enums like this were either hidden entirely from the public-facing headers, prefixed (e.g., SURICATA_TCP_ESTABLISHED), or guarded with an #ifndef _NETINET_TCP_H check to ensure frictionless embedding! (P.S.: I have Suricata 8.0.4)

Thanks Victor for pointing me to StreamTcpInlineMode().

I have just pushed the changes:

Do not ACK flush in inline mode and add ACKwindow config option

ACK flushing is not needed if Suricata is in inline mode. The user can
set inline mode in suricata.yaml.
Also, we let the user set the ACK window size via the new ACKwindow
config option (0-65535 bytes) in c-icap.conf. Setting ACKwindow to 0
disables ACK flushing in IDS mode too.

I would just make this hard coded. The main diff when dealing with packets is that in IDS mode we can still get retransmissions with different data that we have to account for. In IDS mode we try to handle that like the destination OS. In IPS we accept the first data and then rewrite packets on the write to match that if there is a retransmission with different data. I don’t think in your use case you’ll have to deal with this, as this is already handled by the OS? So I think just forcing it to use the inline mode is fine.

As the header suggests, this was meant to be private. We’ll have to see how it gets to be public anyway.

UPDATE: HTTP/2 Support in SSLproxy and icapsuricata

Hi All,

As promised, I have successfully added HTTP/2 support to the ICAP architecture. While this is still a work in progress, it stands as a concrete Proof of Concept (PoC) proving the feasibility of the H2 objectives outlined in my initial ICAP proposal.

Here is a breakdown of the current state of H2 implementation:

  • ALPN Negotiation: SSLproxy now supports ALPN negotiation to seamlessly upgrade HTTPS connections to H2. This works both with and without ICAP support enabled (there is no downgrade back to H1 when running in Divert mode).
  • Protocol vs. Content Inspection: icapsuricata does not support H2, nor does it need to. It is explicitly designed for content inspection rather than protocol inspection.
  • Header Translation & Multiplexing: The ICAP subsystem within SSLproxy fully supports H2 stream multiplexing. It translates inbound H2 headers into H1 before routing them to icapsuricata, and maps the H1 headers in the ICAP responses back into standard H2 headers on the return path.
  • Parallel Processing: icapsuricata features full parallel access to libsuricata by leveraging thread-local context variables mapped to each c-icap worker thread. This completely eliminates global locking and performance serialization bottlenecks.
  • Stream Tracking via TCP Option 78: To ensure Suricata can distinguish between concurrent H2 streams, icapsuricata uses the ICAP client port as the source port for its emulated TCP packet injection. Simultaneously, it embeds the actual client port directly into the emulated packet as a standard TCP Option Kind 78 (historically used for enterprise proxy signaling).

Testing & Validation

The architecture has been rigorously tested utilizing a chain of 5 ICAP services sequentially per H2 stream (icapsuricata, E2g, squidclamav, c-icap echo, and ex206). Validation was performed using both curl and standard web browsers, handling anywhere from 3 to 11 interleaved streams per H2 connection initially, and successfully scaling up to hundreds of concurrent streams over repeated page refreshes.

You can view the implementation details across both repositories on GitHub:

Major TODO Items

  • Enhanced H2 Filtering: Implement HTTP filtering rules targeted at Host and URI headers inside individual H2 streams (other baseline filtering rule structures already work natively with H2 connections).
  • Live Integration: Integrate the subsystem into UTMFW and begin testing against live production traffic patterns.
  • HTTP/3 Support: Begin preliminary architecture design for HTTP/3 over UDP.

UPDATE: HTTP/3 and QUIC Support in SSLproxy and icapsuricata

Hi All,

As promised, I have successfully added HTTP/3 and QUIC support to the ICAP architecture. This PoC now fully demonstrates the feasibility of the objectives outlined in my initial proposal, covering ICAP, HTTP/2, and HTTP/3. QED.

Current State of HTTP/3 Implementation

The state of the HTTP/3 implementation closely parallels our HTTP/2 work outlined in the previous update:

  • Protocol Handling & Multiplexing: SSLproxy fully supports HTTP/3 and stream multiplexing over QUIC, operating seamlessly with or without ICAP enabled.
  • Header Translation & Decoupling: icapsuricata does not need native HTTP/3 support because it focuses purely on content inspection. When ICAP is enabled, the ICAP subsystem inside SSLproxy translates inbound HTTP/3 streams and headers into HTTP/1.1 before routing them to icapsuricata. On the return path, it maps the HTTP/1.1 headers in the ICAP responses back into standard HTTP/3 frames.
  • Testing & Scale: HTTP/3 support was validated using the same chain of 5 ICAP services and test suites as HTTP/2. The implementation successfully handles stream multiplexing and scales up to hundreds of streams across repeated page refreshes.

Dependencies & Build Considerations

Because HTTP/3 requires recent network libraries, it introduces specific environment requirements:

  • Libraries: Demands recent versions of OpenSSL (3.4+), ngtcp2, and nghttp3, which are not yet available in all Linux distribution repositories.
  • Testing Setup: Testing requires a curl binary built with HTTP/3 support, an H3-capable upstream server (such as Caddy, as Apache2 lacks native H3 support), and specific command-line flags in browsers like Brave to force HTTP/3 against the target domain.

Due to these external dependency requirements, HTTP/3 support is currently disabled by default via the WITHOUT_HTTP3 build switch.

Codebase & Implementation Details

You can review the updated implementation across both repositories on GitHub:

Major TODO Items Still Pending

  • Enhanced H2/H3 Filtering: Implement HTTP filtering rules targeted at Host and URI headers inside individual H2/H3 streams (other baseline filtering rule structures already work natively with H2/H3 connections).
  • Logging Integration: Fully integrate the logging subsystem to the H2/H3 code and the ICAP subsystem for content, pcap, mirror, and cert logging.
  • Live Integration: Integrate the complete subsystem into UTMFW and begin testing against live, high-throughput production traffic patterns.

These are sample lines to add to sslproxy.conf, assuming you have built/setup the test env with H3 support:

# 5 ICAP services in chain
Icap icap://127.0.0.1:1344,squidclamav,squidclamav,yes,yes,10,1024
Icap icap://127.0.0.1:1344,ex206,ex206,yes,yes,10,1024
Icap icap://127.0.0.1:1344,echo,echo,yes,yes,10,256
Icap icap://127.0.0.1:1345,reqmod,respmod,yes,yes,10,0,0,no,no,X-ICAP-E2G
Icap icap://127.0.0.1:1344,suricata,suricata,yes,yes,10,1024,0,yes,no,X-Response-Vars

# H3/QUIC at 8444, Caddy at 8443 (UDP)
ProxySpec http3 127.0.0.1 8444 127.0.0.1 8443

# Point H1/H2 clients to upgrade to H3 proxy above
RewriteAltSvcPort 8444

# H1 and H2 at the same H3 port numbers (TCP)
ProxySpec https 127.0.0.1 8444 127.0.0.1 8443

SSLproxy now has UDP support in pcap and mirror content logging, so that we can record decrypted QUIC traffic in a pcap file or mirror it to an interface. For a single HTTP/3 GET request, the pcap/mirror logs simply contain 2x UDP datagrams with a GET request and a 200 response. (H3 headers are QPACK encoded, hence not human readable in the logs.)

Wireshark decodes the traffic as QUIC, but since the logs lack the Initial Handshake secrets, traffic keys, and uni streams, it also gives “[Unknown QUIC connection. Missing Initial Packet or migrated connection?]”.

Suricata with the -r option detects this traffic as QUIC, after enabling quic app-layer parser in suricata.yaml and setting its detection-ports to the HTTP/3 server port in the pcap file (8443 below).

For the quic signature below:

alert quic any any -> any any (msg:"QUIC content Match H2/H3 Multiplexing Test"; content:"H2/H3 Multiplexing Test"; flow:to_client; sid:9000006; rev:1;)

Suricata issues an alert in fast.log:

08/15/2026-21:28:19.405707  [**] [1:9000006:1] QUIC content Match H2/H3 Multiplexing Test [**] [Classification: (null)] [Priority: 3] {UDP} 127.0.0.1:8443 -> 127.0.0.1:41033

And this can be seen in eve.json as well:

{
    "timestamp": "2026-08-15T21:28:19.405076+0300",
    "flow_id": 895365124542975,
    "pcap_cnt": 1,
    "event_type": "quic",
    "src_ip": "127.0.0.1",
    "src_port": 41033,
    "dest_ip": "127.0.0.1",
    "dest_port": 8443,
    "proto": "UDP",
    "ip_v": 4,
    "pkt_src": "wire/pcap",
    "quic": {
        "version": "200000d1"
    }
}
{
    "timestamp": "2026-08-15T21:28:19.405707+0300",
    "flow_id": 895365124542975,
    "pcap_cnt": 2,
    "event_type": "alert",
    "src_ip": "127.0.0.1",
    "src_port": 8443,
    "dest_ip": "127.0.0.1",
    "dest_port": 41033,
    "proto": "UDP",
    "ip_v": 4,
    "pkt_src": "wire/pcap",
    "tx_id": 1,
    "alert": {
        "action": "allowed",
        "gid": 1,
        "signature_id": 9000006,
        "rev": 1,
        "signature": "QUIC content Match H2/H3 Multiplexing Test",
        "category": "",
        "severity": 3
    },
    "quic": {
        "version": "405f0000"
    },
    "app_proto": "quic",
    "direction": "to_client",
    "flow": {
        "pkts_toserver": 1,
        "pkts_toclient": 1,
        "bytes_toserver": 76,
        "bytes_toclient": 678,
        "start": "2026-08-15T21:28:19.405076+0300",
        "src_ip": "127.0.0.1",
        "dest_ip": "127.0.0.1",
        "src_port": 41033,
        "dest_port": 8443
    }
}
{
    "timestamp": "2026-08-15T21:28:19.405707+0300",
    "flow_id": 895365124542975,
    "pcap_cnt": 2,
    "event_type": "quic",
    "src_ip": "127.0.0.1",
    "src_port": 8443,
    "dest_ip": "127.0.0.1",
    "dest_port": 41033,
    "proto": "UDP",
    "ip_v": 4,
    "pkt_src": "wire/pcap",
    "quic": {
        "version": "405f0000"
    }
}
{
    "timestamp": "2026-08-15T21:28:19.405076+0300",
    "flow_id": 895365124542975,
    "event_type": "flow",
    "src_ip": "127.0.0.1",
    "src_port": 41033,
    "dest_ip": "127.0.0.1",
    "dest_port": 8443,
    "ip_v": 4,
    "proto": "UDP",
    "app_proto": "quic",
    "flow": {
        "pkts_toserver": 1,
        "pkts_toclient": 1,
        "bytes_toserver": 76,
        "bytes_toclient": 678,
        "start": "2026-08-15T21:28:19.405076+0300",
        "end": "2026-08-15T21:28:19.405707+0300",
        "age": 0,
        "state": "established",
        "reason": "shutdown",
        "alerted": true,
        "tx_cnt": 2
    }
}
{
    "timestamp": "2026-08-15T21:28:39.844823+0300",
    "event_type": "stats",
    "stats": {
        "uptime": 0,
        "decoder": {
            "pkts": 2,
            "bytes": 754,
            "invalid": 0,
            "ipv4": 2,
            "ipv6": 0,
            "ethernet": 2,
            "arp": 0,
            "unknown_ethertype": 0,
            "chdlc": 0,
            "raw": 0,
            "null": 0,
            "sll": 0,
            "sll2": 0,
            "tcp": 0,
            "udp": 2,
            "sctp": 0,
            "esp": 0,
            "icmpv4": 0,
            "icmpv6": 0,
            "ppp": 0,
            "pppoe": 0,
            "geneve": 0,
            "gre": 0,
            "vlan": 0,
            "vlan_qinq": 0,
            "vlan_qinqinq": 0,
            "vxlan": 0,
            "vntag": 0,
            "ieee8021ah": 0,
            "teredo": 0,
            "ipv4_in_ipv4": 0,
            "ipv6_in_ipv4": 0,
            "ipv4_in_ipv6": 0,
            "ipv6_in_ipv6": 0,
            "mpls": 0,
            "avg_pkt_size": 377,
            "max_pkt_size": 678,
            "max_mac_addrs_src": 0,
            "max_mac_addrs_dst": 0,
            "erspan": 0,
            "nsh": 0,
            "event": {
                "afpacket": {
                    "trunc_pkt": 0
                },
                "ipv4": {
                    "pkt_too_small": 0,
                    "hlen_too_small": 0,
                    "iplen_smaller_than_hlen": 0,
                    "trunc_pkt": 0,
                    "opt_invalid": 0,
                    "opt_invalid_len": 0,
                    "opt_malformed": 0,
                    "opt_pad_required": 0,
                    "opt_eol_required": 0,
                    "opt_duplicate": 0,
                    "opt_unknown": 0,
                    "wrong_ip_version": 0,
                    "icmpv6": 0,
                    "frag_pkt_too_large": 0,
                    "frag_overlap": 0,
                    "frag_ignored": 0
                },
                "icmpv4": {
                    "pkt_too_small": 0,
                    "unknown_type": 0,
                    "unknown_code": 0,
                    "ipv4_trunc_pkt": 0,
                    "ipv4_unknown_ver": 0
                },
                "icmpv6": {
                    "unknown_type": 0,
                    "unknown_code": 0,
                    "pkt_too_small": 0,
                    "ipv6_unknown_version": 0,
                    "ipv6_trunc_pkt": 0,
                    "mld_message_with_invalid_hl": 0,
                    "unassigned_type": 0,
                    "experimentation_type": 0
                },
                "ipv6": {
                    "pkt_too_small": 0,
                    "trunc_pkt": 0,
                    "trunc_exthdr": 0,
                    "exthdr_dupl_fh": 0,
                    "exthdr_useless_fh": 0,
                    "exthdr_dupl_rh": 0,
                    "exthdr_dupl_hh": 0,
                    "exthdr_dupl_dh": 0,
                    "exthdr_dupl_ah": 0,
                    "exthdr_dupl_eh": 0,
                    "exthdr_invalid_optlen": 0,
                    "wrong_ip_version": 0,
                    "exthdr_ah_res_not_null": 0,
                    "hopopts_unknown_opt": 0,
                    "hopopts_only_padding": 0,
                    "dstopts_unknown_opt": 0,
                    "dstopts_only_padding": 0,
                    "rh_type_0": 0,
                    "zero_len_padn": 0,
                    "fh_non_zero_reserved_field": 0,
                    "data_after_none_header": 0,
                    "unknown_next_header": 0,
                    "icmpv4": 0,
                    "frag_pkt_too_large": 0,
                    "frag_overlap": 0,
                    "frag_invalid_length": 0,
                    "frag_ignored": 0,
                    "ipv4_in_ipv6_too_small": 0,
                    "ipv4_in_ipv6_wrong_version": 0,
                    "ipv6_in_ipv6_too_small": 0,
                    "ipv6_in_ipv6_wrong_version": 0
                },
                "tcp": {
                    "pkt_too_small": 0,
                    "hlen_too_small": 0,
                    "invalid_optlen": 0,
                    "opt_invalid_len": 0,
                    "opt_duplicate": 0
                },
                "udp": {
                    "pkt_too_small": 0,
                    "hlen_too_small": 0,
                    "hlen_invalid": 0,
                    "len_invalid": 0
                },
                "sll": {
                    "pkt_too_small": 0
                },
                "sll2": {
                    "pkt_too_small": 0
                },
                "ethernet": {
                    "pkt_too_small": 0,
                    "unknown_ethertype": 0
                },
                "ppp": {
                    "pkt_too_small": 0,
                    "vju_pkt_too_small": 0,
                    "ip4_pkt_too_small": 0,
                    "ip6_pkt_too_small": 0,
                    "wrong_type": 0,
                    "unsup_proto": 0
                },
                "pppoe": {
                    "pkt_too_small": 0,
                    "wrong_code": 0,
                    "malformed_tags": 0
                },
                "gre": {
                    "pkt_too_small": 0,
                    "wrong_version": 0,
                    "version0_recur": 0,
                    "version0_flags": 0,
                    "version0_hdr_too_big": 0,
                    "version0_malformed_sre_hdr": 0,
                    "version1_chksum": 0,
                    "version1_route": 0,
                    "version1_ssr": 0,
                    "version1_recur": 0,
                    "version1_flags": 0,
                    "version1_no_key": 0,
                    "version1_wrong_protocol": 0,
                    "version1_malformed_sre_hdr": 0,
                    "version1_hdr_too_big": 0
                },
                "vlan": {
                    "header_too_small": 0,
                    "unknown_type": 0,
                    "too_many_layers": 0
                },
                "ieee8021ah": {
                    "header_too_small": 0
                },
                "vntag": {
                    "header_too_small": 0,
                    "unknown_type": 0
                },
                "ipraw": {
                    "invalid_ip_version": 0
                },
                "ltnull": {
                    "pkt_too_small": 0,
                    "unsupported_type": 0
                },
                "sctp": {
                    "pkt_too_small": 0
                },
                "esp": {
                    "pkt_too_small": 0
                },
                "mpls": {
                    "header_too_small": 0,
                    "pkt_too_small": 0,
                    "bad_label_router_alert": 0,
                    "bad_label_implicit_null": 0,
                    "bad_label_reserved": 0,
                    "unknown_payload_type": 0
                },
                "vxlan": {
                    "unknown_payload_type": 0
                },
                "geneve": {
                    "unknown_payload_type": 0
                },
                "erspan": {
                    "header_too_small": 0,
                    "unsupported_version": 0,
                    "too_many_vlan_layers": 0
                },
                "dce": {
                    "pkt_too_small": 0
                },
                "chdlc": {
                    "pkt_too_small": 0
                },
                "nsh": {
                    "header_too_small": 0,
                    "unsupported_version": 0,
                    "bad_header_length": 0,
                    "reserved_type": 0,
                    "unsupported_type": 0,
                    "unknown_payload": 0
                }
            },
            "too_many_layers": 0
        },
        "tcp": {
            "syn": 0,
            "synack": 0,
            "rst": 0,
            "urg": 0,
            "active_sessions": 0,
            "sessions": 0,
            "ssn_memcap_drop": 0,
            "ssn_from_cache": 0,
            "ssn_from_pool": 0,
            "pseudo": 0,
            "invalid_checksum": 0,
            "midstream_pickups": 0,
            "pkt_on_wrong_thread": 0,
            "ack_unseen_data": 0,
            "segment_memcap_drop": 0,
            "segment_from_cache": 0,
            "segment_from_pool": 0,
            "stream_depth_reached": 0,
            "reassembly_gap": 0,
            "overlap": 0,
            "overlap_diff_data": 0,
            "insert_data_normal_fail": 0,
            "insert_data_overlap_fail": 0,
            "urgent_oob_data": 0,
            "memuse": 20971520,
            "reassembly_memuse": 3670016
        },
        "flow": {
            "memcap": 0,
            "total": 1,
            "active": 0,
            "tcp": 0,
            "udp": 1,
            "icmpv4": 0,
            "icmpv6": 0,
            "tcp_reuse": 0,
            "elephant": 0,
            "get_used": 0,
            "get_used_eval": 0,
            "get_used_eval_reject": 0,
            "get_used_eval_busy": 0,
            "get_used_failed": 0,
            "wrk": {
                "spare_sync_avg": 100,
                "spare_sync": 1,
                "spare_sync_incomplete": 0,
                "spare_sync_empty": 0,
                "flows_evicted_needs_work": 0,
                "flows_evicted_pkt_inject": 0,
                "flows_evicted": 0,
                "flows_injected": 0,
                "flows_injected_max": 0
            },
            "end": {
                "state": {
                    "new": 0,
                    "established": 1,
                    "closed": 0,
                    "local_bypassed": 0
                },
                "tcp_state": {
                    "none": 0,
                    "syn_sent": 0,
                    "syn_recv": 0,
                    "established": 0,
                    "fin_wait1": 0,
                    "fin_wait2": 0,
                    "time_wait": 0,
                    "last_ack": 0,
                    "close_wait": 0,
                    "closing": 0,
                    "closed": 0
                },
                "tcp_liberal": 0
            },
            "mgr": {
                "full_hash_pass": 0,
                "rows_per_sec": 20316,
                "rows_maxlen": 0,
                "flows_checked": 0,
                "flows_notimeout": 0,
                "flows_timeout": 0,
                "flows_evicted": 0,
                "flows_evicted_needs_work": 0
            },
            "spare": 9900,
            "emerg_mode_entered": 0,
            "emerg_mode_over": 0,
            "recycler": {
                "recycled": 1,
                "queue_avg": 0,
                "queue_max": 1
            },
            "memuse": 7154304
        },
        "defrag": {
            "ipv4": {
                "fragments": 0,
                "reassembled": 0
            },
            "ipv6": {
                "fragments": 0,
                "reassembled": 0
            },
            "max_trackers_reached": 0,
            "max_frags_reached": 0,
            "tracker_soft_reuse": 0,
            "tracker_hard_reuse": 0,
            "wrk": {
                "tracker_timeout": 0
            },
            "mgr": {
                "tracker_timeout": 0
            },
            "memuse": 33554432
        },
        "flow_bypassed": {
            "local_pkts": 0,
            "local_bytes": 0,
            "local_capture_pkts": 0,
            "local_capture_bytes": 0,
            "closed": 0,
            "pkts": 0,
            "bytes": 0
        },
        "detect": {
            "engines": [
                {
                    "id": 0,
                    "last_reload": "2026-08-15T21:28:39.321158+0300",
                    "rules_loaded": 4,
                    "rules_failed": 0,
                    "rules_skipped": 0
                }
            ],
            "alert": 1,
            "alert_queue_overflow": 0,
            "alerts_suppressed": 0,
            "lua": {
                "errors": 0,
                "blocked_function_errors": 0,
                "instruction_limit_errors": 0,
                "memory_limit_errors": 0
            }
        },
        "app_layer": {
            "flow": {
                "failed_tcp": 0,
                "http": 0,
                "ftp": 0,
                "smtp": 0,
                "tls": 0,
                "ssh": 0,
                "imap": 0,
                "smb": 0,
                "dcerpc_tcp": 0,
                "dns_tcp": 0,
                "nfs_tcp": 0,
                "ntp": 0,
                "ftp-data": 0,
                "tftp": 0,
                "ike": 0,
                "krb5_tcp": 0,
                "quic": 1,
                "dhcp": 0,
                "sip_tcp": 0,
                "rfb": 0,
                "mqtt": 0,
                "telnet": 0,
                "websocket": 0,
                "ldap_tcp": 0,
                "doh2": 0,
                "rdp": 0,
                "http2": 0,
                "bittorrent-dht": 0,
                "pop3": 0,
                "mdns": 0,
                "snmp": 0,
                "failed_udp": 0,
                "dcerpc_udp": 0,
                "dns_udp": 0,
                "nfs_udp": 0,
                "krb5_udp": 0,
                "sip_udp": 0,
                "ldap_udp": 0
            },
            "error": {
                "failed_tcp": {
                    "gap": 0
                },
                "http": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "ftp": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "smtp": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "tls": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "ssh": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "smb": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "dcerpc_tcp": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "dns_tcp": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "nfs_tcp": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "ftp-data": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "krb5_tcp": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "sip_tcp": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "rfb": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "mqtt": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "telnet": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "websocket": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "ldap_tcp": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "doh2": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "rdp": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "http2": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "pop3": {
                    "gap": 0,
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "dcerpc_udp": {
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "dns_udp": {
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "nfs_udp": {
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "ntp": {
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "tftp": {
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "ike": {
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "krb5_udp": {
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "quic": {
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "dhcp": {
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "sip_udp": {
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "ldap_udp": {
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "bittorrent-dht": {
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "mdns": {
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                },
                "snmp": {
                    "alloc": 0,
                    "parser": 0,
                    "internal": 0
                }
            },
            "tx": {
                "http": 0,
                "ftp": 0,
                "smtp": 0,
                "tls": 0,
                "ssh": 0,
                "smb": 0,
                "dcerpc_tcp": 0,
                "dns_tcp": 0,
                "nfs_tcp": 0,
                "ftp-data": 0,
                "krb5_tcp": 0,
                "sip_tcp": 0,
                "rfb": 0,
                "mqtt": 0,
                "telnet": 0,
                "websocket": 0,
                "ldap_tcp": 0,
                "doh2": 0,
                "rdp": 0,
                "http2": 0,
                "pop3": 0,
                "dcerpc_udp": 0,
                "dns_udp": 0,
                "nfs_udp": 0,
                "ntp": 0,
                "tftp": 0,
                "ike": 0,
                "krb5_udp": 0,
                "quic": 2,
                "dhcp": 0,
                "sip_udp": 0,
                "ldap_udp": 0,
                "bittorrent-dht": 0,
                "mdns": 0,
                "snmp": 0
            },
            "expectations": 0
        },
        "memcap": {
            "pressure": 31,
            "pressure_max": 31
        },
        "http": {
            "memuse": 0,
            "memcap": 0,
            "byterange": {
                "memuse": 168384,
                "memcap": 104857600
            }
        },
        "ftp": {
            "memuse": 0,
            "memcap": 0
        },
        "ippair": {
            "memuse": 398144,
            "memcap": 16777216
        },
        "host": {
            "memuse": 382144,
            "memcap": 33554432
        },
        "file_store": {
            "open_files": 0
        }
    }
}

SSLproxy now supports HTTP/2 and HTTP/3 stream filtering with Host and URI rules.

For example, in my test env, I have a test index.html page with 9x tile_*.png images and a favicon.ico, which the Brave web browser fetches using 11 streams over the same H2 or H3 connection.

So, when I use the following H3 proxyspec in sslproxy.conf:

ProxySpec {
	Proto http3

	Addr 127.0.0.1
	Port 8444

	Divert no

	TargetAddr 127.0.0.1
	TargetPort 8443

    Block to uri tile_1.png*

    FilterRule {
        Action Match
        URI tile_2.png*

        Icap icap://127.0.0.1:1344,suricata,suricata,yes,yes,10,1024,0,yes,no,X-Response-Vars
    }
}
  1. SSLproxy blocks the stream, and only that stream, for tile_1.png.
  2. It sends ICAP requests to icapsuricata for the stream, and only that stream, for tile_2.png.
  3. The other streams pass through without ICAP inspection.

This works the same for H2 as well, of course.

In short, you can use SSLproxy’s stream filtering rules and icapsuricata to selectively inspect certain streams in H2/H3 connections with Suricata now.