Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "PcapTools"
uuid = "222fe7e8-3f39-464a-bf97-d9bbb753f246"
authors = ["Christian Rorvik <christian.rorvik@gmail.com>"]
version = "1.4.0"
version = "1.5.0"

[deps]
CRC32 = "b4567568-9dcc-467e-9b62-c342d3a501d3"
Expand Down
4 changes: 3 additions & 1 deletion src/PcapTools.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ export PcapRecord
export PcapReader, PcapStreamReader, PcapBufferReader
export PcapWriter, PcapStreamWriter
export LINKTYPE_NULL, LINKTYPE_ETHERNET
export ETHERNET_FCS_LENGTH
export FCSPresence, FCS_PRESENT, FCS_ABSENT, FCS_UNDETERMINED
export splitcap
export pcap_has_fcs, check_fcs, compute_fcs, ETHERNET_FCS_LENGTH
export try_detect_fcs, pcap_has_fcs, check_fcs, compute_fcs

abstract type PcapReader end
abstract type PcapWriter end
Expand Down
27 changes: 20 additions & 7 deletions src/fcs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ const ETHERNET_ETHERTYPE_OFFSET = 12
const ETHERTYPE_IPV4 = UInt16(0x0800)
const IP_TOTAL_LENGTH_OFFSET = ETHERNET_HEADER_SIZE + 2

@enum FCSPresence FCS_PRESENT FCS_ABSENT FCS_UNDETERMINED

"""
pcap_has_fcs(::PcapReader)
try_detect_fcs(::PcapReader; confirm_checksum = true) -> FcsStatus

Heuristically determine whether captured frames contain Ethernet FCS or not.
Unfortunately, PCAP format doesn't provide this information explicitly.

By default, potential FCS frames have their checksum recomputed as additional
confirmation: disable this with `confirm_checksum = false`.
"""
function pcap_has_fcs(reader::PcapReader)
function try_detect_fcs(reader::PcapReader; confirm_checksum::Bool = true)
mark(reader)
try
while !eof(reader)
Expand All @@ -31,16 +36,17 @@ function pcap_has_fcs(reader::PcapReader)
ip_total_length = GC.@preserve record unsafe_load(convert(Ptr{UInt16}, frame + IP_TOTAL_LENGTH_OFFSET))
ip_total_length = ntoh(ip_total_length)
if ip_total_length + ETHERNET_HEADER_SIZE + ETHERNET_FCS_LENGTH == hdr.orig_len
return true
if !confirm_checksum || check_fcs(record)
return FCS_PRESENT
end
elseif ip_total_length + ETHERNET_HEADER_SIZE == hdr.orig_len
return false
return FCS_ABSENT
end
end
# if we couldn't read single IP packet, it doesn't really matter what to return
return false
return FCS_UNDETERMINED
catch e
if e isa EOFError
return false
return FCS_UNDETERMINED
else
rethrow()
end
Expand All @@ -49,6 +55,13 @@ function pcap_has_fcs(reader::PcapReader)
end
end


"""
pcap_has_fcs(::PcapReader; confirm_checksum = true) -> Union{Nothing, Bool}
"""
pcap_has_fcs(reader::PcapReader; kwargs...) = try_detect_fcs(reader; kwargs...) == FCS_PRESENT


"""
compute_fcs(x::PcapRecord) -> UInt32

Expand Down
65 changes: 52 additions & 13 deletions test/fcs_tests.jl
Original file line number Diff line number Diff line change
@@ -1,29 +1,68 @@
const PCAP_FCS = base64decode("""
1MOyoQIABAAAAAAAAAAAAABAAAABAAAATHLDZMwhDABeAAAAXgAAAAEAXgByrQDXj6hWAQgARQAA
const PCAP_FILE_HEADER = "TTyyoQIABAAAAAAAAAAAAAAGAAABAAAA"

const PCAP_FCS = """
THLDZMwhDABeAAAAXgAAAAEAXgByrQDXj6hWAQgARQAA
TGk3QAD9Eag2wR1YZ+AAcq1KeuaZADhwtSAAyDL/////HxIFAHRgAwAMAQAAAAAAAAzrS61s+HUX
EADJMv////9OAAAAAAAAALygjcY=
""")
"""

const PCAP_NOFCS = base64decode("""
1MOyoQIABAAAAAAAAAAAAAAABAABAAAAablkZZ4hAgBiAAAAYgAAAAABIQIgTBjATYjFhQgARQAA
const PCAP_NOFCS = """
ablkZZ4hAgBiAAAAYgAAAAABIQIgTBjATYjFhQgARQAA
VMmzQABAAVv6CksAZQpLAAEIANTpAAIAAWm5ZGUAAAAAlCECAAAAAAAQERITFBUWFxgZGhscHR4f
ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3
""")
"""

const PCAP_CORRUPT_FCS = base64decode("""
1MOyoQIABAAAAAAAAAAAAABAAAABAAAATHLDZMwhDABeAAAAXgAAAAEAXgByrQDXj6hWAQgARQAA
const PCAP_CORRUPT_FCS = """
THLDZMwhDABeAAAAXgAAAAEAXgByrQDXj6hWAQgARQAA
TGk3QAD9Eag2wR1YZ+AAcq1KeuaZADhwtSAAyDL/////HxIFAHRgAwAMAQAAAAAAAAzrS61s+HUX
EADJMv////9OAAAAAAAAALygjcg=
""")
"""

# Miniumum ethernet packet size (60 bytes without FCS)
const PCAP_SMALL = """
Tq98Zs+xVzM8AAAAPAAAAAEAXgBynQDXj6hFQQgARQAA
HrAOQAD9EV7swR1bGOAAcp3yuObOAAr2hcD4AAAAAAAAAAAAAAAAAAAAAA==
"""

# Non-IPv4 packet (ARP protocol)
const PCAP_NONIP = """
8+/iZolNFC1AAAAAQAAAAAD2Y0DDvGQ/XwHjQwgGAAEI
AAYEAAFkP18B40Na4gKMAAAAAAAAWuICgQAAAAAAAAAAAAAAAAAAAAAAAOr6Tyw=
"""

check_detect_fcs(packets; confirm_checksum=true) =
pushfirst!(packets, PCAP_FILE_HEADER) |>
join |>
base64decode |>
PcapBufferReader |>
(x -> try_detect_fcs(x; confirm_checksum))

@testset "pcap_has_fcs" begin
@test pcap_has_fcs(PcapBufferReader(PCAP_FCS))
@test !pcap_has_fcs(PcapBufferReader(PCAP_NOFCS))
@test pcap_has_fcs(PcapBufferReader(base64decode(PCAP_FILE_HEADER * PCAP_FCS)))
@test !pcap_has_fcs(PcapBufferReader(base64decode(PCAP_FILE_HEADER * PCAP_NOFCS)))
end

@testset "try_detect_fcs" begin
@test check_detect_fcs([PCAP_NONIP]) == FCS_UNDETERMINED
@test check_detect_fcs([PCAP_SMALL]) == FCS_UNDETERMINED
@test check_detect_fcs([""]) == FCS_UNDETERMINED # empty pcap

@test check_detect_fcs([PCAP_NONIP, PCAP_SMALL, PCAP_FCS]) == FCS_PRESENT
@test check_detect_fcs([PCAP_NONIP, PCAP_SMALL, PCAP_NOFCS]) == FCS_ABSENT

# Corrupt FCS packets will be ignored by default
@test check_detect_fcs([PCAP_NONIP, PCAP_SMALL, PCAP_CORRUPT_FCS]) == FCS_UNDETERMINED
# But corrupt FCS can be explicitly allowed
@test check_detect_fcs([PCAP_NONIP, PCAP_SMALL, PCAP_CORRUPT_FCS]; confirm_checksum = false) == FCS_PRESENT

# Make sure we skip corrupt frames if confirm_checksum is on
@test check_detect_fcs([PCAP_NONIP, PCAP_SMALL, PCAP_CORRUPT_FCS, PCAP_FCS]) == FCS_PRESENT
@test check_detect_fcs([PCAP_NONIP, PCAP_SMALL, PCAP_CORRUPT_FCS, PCAP_NOFCS]) == FCS_ABSENT
end

@testset "compute_fcs" begin
r_fcs = read(PcapBufferReader(PCAP_FCS))
r_fcs = read(PcapBufferReader(base64decode(PCAP_FILE_HEADER * PCAP_FCS)))
@test check_fcs(r_fcs)
r_no_fcs = read(PcapBufferReader(PCAP_CORRUPT_FCS))
r_no_fcs = read(PcapBufferReader(base64decode(PCAP_FILE_HEADER * PCAP_CORRUPT_FCS)))
@test !check_fcs(r_no_fcs)
end