Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions examples/generate_pcapng.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,7 @@
spb = shb.new_member(blocks.SimplePacket)
spb.packet_data = bytes(test_pl)
writer.write_block(spb)

jeb = shb.new_member(blocks.SystemdJournalExport)
spb.journal_entry = bytes("__REALTIME_TIMESTAMP=0\nMESSAGE=Hello!\n", "utf-8")
writer.write_block(jeb)
24 changes: 23 additions & 1 deletion pcapng/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from pcapng.constants import link_types
from pcapng.structs import (
IntField,
JournalEntryField,
ListField,
NameResolutionRecordField,
Option,
Expand Down Expand Up @@ -47,7 +48,10 @@ class Block(object):
def __init__(self, **kwargs):
if "raw" in kwargs:
self._decoded = struct_decode(
self.schema, io.BytesIO(kwargs["raw"]), kwargs["endianness"]
self.schema,
io.BytesIO(kwargs["raw"]),
kwargs["endianness"],
len(kwargs["raw"]),
)
else:
self._decoded = {}
Expand Down Expand Up @@ -650,6 +654,24 @@ class InterfaceStatistics(
]


@register_block
class SystemdJournalExport(SectionMemberBlock):
"""
"The systemd Journal Export Block is a lightweight container for systemd
Journal Export Format entry data. [...] Although the primary use of this
block is intended for importing data from systemd, it could potentially
be used to include arbitrary key-value data in a capture file."
- pcapng spec, section 4.7. Other quoted citations are from this section
unless otherwise noted.
"""

magic_number = 0x00000009
__slots__ = []
schema = [
("journal_entry", JournalEntryField(), b""),
]


class UnknownBlock(Block):
"""
Class used to represent an unknown block.
Expand Down
53 changes: 41 additions & 12 deletions pcapng/structs.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ class StructField(object):
__slots__ = []

@abc.abstractmethod
def load(self, stream, endianness, seen=None):
def load(self, stream, endianness, max_size=None, seen=None):
pass

def __repr__(self):
Expand All @@ -317,7 +317,7 @@ class RawBytes(StructField):
def __init__(self, size):
self.size = size # in bytes!

def load(self, stream, endianness=None, seen=None):
def load(self, stream, endianness=None, max_size=None, seen=None):
return read_bytes_padded(stream, self.size)

def encode(self, value, stream, endianness=None):
Expand All @@ -343,7 +343,7 @@ def __init__(self, size, signed=False):
self.size = size # in bits!
self.signed = signed

def load(self, stream, endianness, seen=None):
def load(self, stream, endianness, max_size=None, seen=None):
number = read_int(stream, self.size, signed=self.signed, endianness=endianness)
return number

Expand Down Expand Up @@ -372,7 +372,7 @@ class OptionsField(StructField):
def __init__(self, options_schema):
self.options_schema = options_schema

def load(self, stream, endianness, seen=None):
def load(self, stream, endianness, max_size=None, seen=None):
options = read_options(stream, endianness)
return Options(schema=self.options_schema, data=options, endianness=endianness)

Expand Down Expand Up @@ -400,7 +400,7 @@ class PacketBytes(StructField):
def __init__(self, len_field):
self.dependency = len_field

def load(self, stream, endianness, seen=[]):
def load(self, stream, endianness, max_size=None, seen=[]):
try:
length = seen[self.dependency]
except TypeError:
Expand Down Expand Up @@ -445,7 +445,7 @@ class ListField(StructField):
def __init__(self, subfield):
self.subfield = subfield

def load(self, stream, endianness, seen=None):
def load(self, stream, endianness, max_size=None, seen=None):
return list(self._iter_load(stream, endianness))

def _iter_load(self, stream, endianness):
Expand Down Expand Up @@ -487,7 +487,7 @@ class NameResolutionRecordField(StructField):

__slots__ = []

def load(self, stream, endianness, seen=None):
def load(self, stream, endianness, max_size=None, seen=None):
record_type = read_int(stream, 16, False, endianness)
record_length = read_int(stream, 16, False, endianness)

Expand Down Expand Up @@ -538,6 +538,26 @@ def encode_finish(self, stream, endianness):
write_int(0, stream, 16, endianness=endianness)


class JournalEntryField(StructField):
"""
Field containing a "journal entry", used in the SystemdJournalExport block.
"""

def load(self, stream, endianness, max_size, seen=None):
# Slurp all remaining bytes.
data = read_bytes_padded(stream, max_size)

# Drop all trailing padding bytes.
data = data.rstrip(b"\x00")

return data

def encode(self, data, stream, endianness=None):
if not data:
raise ValueError("Journal entry invalid")
write_bytes_padded(stream, data)


def read_options(stream, endianness):
"""
Read "options" from an "options block" in a stream, until a
Expand Down Expand Up @@ -1005,7 +1025,7 @@ def _encode_value(self, value, ftype):
raise ValueError("Unsupported field type: {0}".format(ftype))


def struct_decode(schema, stream, endianness="="):
def struct_decode(schema, stream, endianness="=", max_size=None):
"""
Decode structured data from a stream, following a schema.

Expand All @@ -1024,18 +1044,27 @@ def struct_decode(schema, stream, endianness="="):
endianness specifier, as accepted by Python struct module
(one of ``<>!=``, defaults to ``=``).

:param max_size:
maximum number of bytes to read, None for infinity.

:return:
a dictionary mapping the field names to decoded data
"""

decoded = {}
prev_stream_pos = stream.tell()
for name, field, default in schema:
decoded[name] = field.load(stream, endianness=endianness, seen=decoded)
return decoded
decoded[name] = field.load(
stream, endianness=endianness, max_size=max_size, seen=decoded
)

if max_size is not None:
# Update max remaining number of bytes.
current_stream_pos = stream.tell()
max_size -= current_stream_pos - prev_stream_pos
prev_stream_pos = current_stream_pos

def block_decode(block, stream):
return struct_decode(block.schema, stream, block.section.endianness)
return decoded


def struct_encode(schema, obj, outstream, endianness="="):
Expand Down
10 changes: 10 additions & 0 deletions tests/test_write_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ def test_write_read_all_blocks():
writer.write_block(blk)
out_blocks.append(blk)

# systemd Journal Export Block.
blk = o_shb.new_member(
blocks.SystemdJournalExport,
journal_entry=bytes(
"__REALTIME_TIMESTAMP=0\nMESSAGE=Hello!\nPRIORITY=6\n", "utf-8"
),
)
writer.write_block(blk)
out_blocks.append(blk)

# Done writing blocks.
# Now get back what we wrote and see if things line up.
fake_file.seek(0)
Expand Down