Browse documentation

Ruby API

require 'redhound'

Redhound.open('trace.pcapng', filter: 'tcp port 443') do |reader|
  reader.each do |packet|
    puts packet.summary
    puts packet['ip.src']
    p packet[:tcp]&.values
    p packet.to_h
  end
end

Redhound.capture(interface: 'lo', count: 10, filter: 'udp') do |packet|
  puts packet.summary
end

packet = Redhound.dissect(frame_bytes, linktype: :ethernet,
                         timestamp_ns: 1_700_000_000_123_456_789)
Redhound::Writer.open('copy.pcapng') { |writer| writer << packet }

Redhound.open accepts a path, -, or a binary IO. Reader is Enumerable, provides next_packet, stats, interfaces, stop, and close; a block always closes it. Redhound.capture yields packets and closes the live source even if the block fails. Capture options include snaplen, promiscuous, buffer_size (bytes), direction (:in, :out, :inout), backend, and filter. On Linux Ruby 4.0, backend: :auto uses sockets because IO::Buffer.map cannot map packet sockets; explicit :ring raises UnsupportedPlatform. Ruby 3.3/3.4 retain ring support.

next_packet(timeout:) returns nil at its deadline without discarding a partial stdin/pipe record. stop interrupts a waiting read. attach_filter(program) replaces a source’s capture filter; filtering preserves the original wire length. For pcapng input, retained interface metadata is limited to 4,096 interfaces and 16 MiB across sections, with at most 4,096 options in a block. Excess input raises FileFormatError rather than growing memory without a bound.

Redhound.dissect creates an immutable-byte Packet and decodes its layers lazily. Packet metadata includes timestamp_ns, original_length, linktype, interface, direction, and number. caplen, truncated?, and time expose capture properties. A symbol index selects the first Layer; a dotted string selects a decoded field value. layers_of, innermost, and field_values expose repeated or tunneled layers. Layer has values, fields, and diagnostics. to_h follows json-schema.json; bytes use hexadecimal strings, addresses use text, booleans remain booleans, repeated fields become arrays. timestamp_ns must be an Integer; calendar output preserves expanded years outside 0000..9999. number is positive, direction is :in, :out, or nil, and integer link types are restricted to 0..65535. Invalid UTF-8 field text, interface names, and diagnostic text use hexadecimal strings without discarding their original bytes.

Each Field exposes its name, type, value, raw_value, offset, and length. For packet dissection, offsets are absolute captured byte positions; lengths describe the encoded source span. Complete fixed fields remain available when the rest of a header or its options are truncated. Derived values such as tcp.len, data.len, and igmp.version have zero source length. DNS compressed names refer to their encoded name/pointer span. Concatenated DHCP values and TLS handshakes crossing records span the first through last source byte, including intervening TLV/record headers; decoded values need not equal a direct byte slice. Likewise, chunked HTTP body spans include chunk framing. Stream-reassembled application fields use offsets in the logical PDU rather than the completing packet’s data; tcp.reassembled_from identifies the contributing frames.

Writer accepts format: :pcap|:pcapng, linktype, snaplen, precision, packet_buffered, max_bytes, interval, file_count, and post_rotate_command. write/<<, flush, write_stats, and close are available. Use a block for deterministic closure.

Custom dissectors

class Example < Redhound::Dissector
  protocol :example, name: 'Example protocol', short: 'EXAMPLE'
  dissects_on 'udp.port', 9999
  header do
    uint16 :message_id, 'example.id'
    uint8 :kind, 'example.kind'
  end

  def summary(layer)
    "Example id #{layer[:message_id]}"
  end
end

Header fields support unsigned integers, signed int8, MAC/IPv4/IPv6 addresses and bit fields. Use dissect(ctx, layer) for bounded variable-length parsing through ctx.cursor; append fields with layer.add. next_dissector(ctx, layer) returns a registered dissector class, or nil. Child cursors cannot escape their packet bounds. Truncated input creates a diagnostic. REDHOUND_STRICT=1 re-raises unexpected implementation errors for tests. Ordinary malformed input remains safe. Registry copies allow per-session decode-as rules without changing global registrations.

Analysis::Session#update(packet) adds flow/stream analysis and completed application PDUs. It provides bounded flow/reassembly state and snapshot, finish; library users opt into it explicitly. Reassembly does not modify the packet’s captured bytes. Filter.compile(expression, linktype:) returns a validated cBPF Program with match?, evaluate, serialize, and disassemble.

Generate reference documentation with bundle exec yard doc lib/**/*.rb.