Skip to content

Commit 89d7327

Browse files
authored
FCS detection improvements (#9)
1 parent 61fdf87 commit 89d7327

File tree

4 files changed

+76
-22
lines changed

4 files changed

+76
-22
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "PcapTools"
22
uuid = "222fe7e8-3f39-464a-bf97-d9bbb753f246"
33
authors = ["Christian Rorvik <[email protected]>"]
4-
version = "1.4.0"
4+
version = "1.5.0"
55

66
[deps]
77
CRC32 = "b4567568-9dcc-467e-9b62-c342d3a501d3"

src/PcapTools.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ export PcapRecord
1111
export PcapReader, PcapStreamReader, PcapBufferReader
1212
export PcapWriter, PcapStreamWriter
1313
export LINKTYPE_NULL, LINKTYPE_ETHERNET
14+
export ETHERNET_FCS_LENGTH
15+
export FCSPresence, FCS_PRESENT, FCS_ABSENT, FCS_UNDETERMINED
1416
export splitcap
15-
export pcap_has_fcs, check_fcs, compute_fcs, ETHERNET_FCS_LENGTH
17+
export try_detect_fcs, pcap_has_fcs, check_fcs, compute_fcs
1618

1719
abstract type PcapReader end
1820
abstract type PcapWriter end

src/fcs.jl

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,18 @@ const ETHERNET_ETHERTYPE_OFFSET = 12
77
const ETHERTYPE_IPV4 = UInt16(0x0800)
88
const IP_TOTAL_LENGTH_OFFSET = ETHERNET_HEADER_SIZE + 2
99

10+
@enum FCSPresence FCS_PRESENT FCS_ABSENT FCS_UNDETERMINED
11+
1012
"""
11-
pcap_has_fcs(::PcapReader)
13+
try_detect_fcs(::PcapReader; confirm_checksum = true) -> FcsStatus
1214
1315
Heuristically determine whether captured frames contain Ethernet FCS or not.
1416
Unfortunately, PCAP format doesn't provide this information explicitly.
17+
18+
By default, potential FCS frames have their checksum recomputed as additional
19+
confirmation: disable this with `confirm_checksum = false`.
1520
"""
16-
function pcap_has_fcs(reader::PcapReader)
21+
function try_detect_fcs(reader::PcapReader; confirm_checksum::Bool = true)
1722
mark(reader)
1823
try
1924
while !eof(reader)
@@ -31,16 +36,17 @@ function pcap_has_fcs(reader::PcapReader)
3136
ip_total_length = GC.@preserve record unsafe_load(convert(Ptr{UInt16}, frame + IP_TOTAL_LENGTH_OFFSET))
3237
ip_total_length = ntoh(ip_total_length)
3338
if ip_total_length + ETHERNET_HEADER_SIZE + ETHERNET_FCS_LENGTH == hdr.orig_len
34-
return true
39+
if !confirm_checksum || check_fcs(record)
40+
return FCS_PRESENT
41+
end
3542
elseif ip_total_length + ETHERNET_HEADER_SIZE == hdr.orig_len
36-
return false
43+
return FCS_ABSENT
3744
end
3845
end
39-
# if we couldn't read single IP packet, it doesn't really matter what to return
40-
return false
46+
return FCS_UNDETERMINED
4147
catch e
4248
if e isa EOFError
43-
return false
49+
return FCS_UNDETERMINED
4450
else
4551
rethrow()
4652
end
@@ -49,6 +55,13 @@ function pcap_has_fcs(reader::PcapReader)
4955
end
5056
end
5157

58+
59+
"""
60+
pcap_has_fcs(::PcapReader; confirm_checksum = true) -> Union{Nothing, Bool}
61+
"""
62+
pcap_has_fcs(reader::PcapReader; kwargs...) = try_detect_fcs(reader; kwargs...) == FCS_PRESENT
63+
64+
5265
"""
5366
compute_fcs(x::PcapRecord) -> UInt32
5467

test/fcs_tests.jl

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,68 @@
1-
const PCAP_FCS = base64decode("""
2-
1MOyoQIABAAAAAAAAAAAAABAAAABAAAATHLDZMwhDABeAAAAXgAAAAEAXgByrQDXj6hWAQgARQAA
1+
const PCAP_FILE_HEADER = "TTyyoQIABAAAAAAAAAAAAAAGAAABAAAA"
2+
3+
const PCAP_FCS = """
4+
THLDZMwhDABeAAAAXgAAAAEAXgByrQDXj6hWAQgARQAA
35
TGk3QAD9Eag2wR1YZ+AAcq1KeuaZADhwtSAAyDL/////HxIFAHRgAwAMAQAAAAAAAAzrS61s+HUX
46
EADJMv////9OAAAAAAAAALygjcY=
5-
""")
7+
"""
68

7-
const PCAP_NOFCS = base64decode("""
8-
1MOyoQIABAAAAAAAAAAAAAAABAABAAAAablkZZ4hAgBiAAAAYgAAAAABIQIgTBjATYjFhQgARQAA
9+
const PCAP_NOFCS = """
10+
ablkZZ4hAgBiAAAAYgAAAAABIQIgTBjATYjFhQgARQAA
911
VMmzQABAAVv6CksAZQpLAAEIANTpAAIAAWm5ZGUAAAAAlCECAAAAAAAQERITFBUWFxgZGhscHR4f
1012
ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3
11-
""")
13+
"""
1214

13-
const PCAP_CORRUPT_FCS = base64decode("""
14-
1MOyoQIABAAAAAAAAAAAAABAAAABAAAATHLDZMwhDABeAAAAXgAAAAEAXgByrQDXj6hWAQgARQAA
15+
const PCAP_CORRUPT_FCS = """
16+
THLDZMwhDABeAAAAXgAAAAEAXgByrQDXj6hWAQgARQAA
1517
TGk3QAD9Eag2wR1YZ+AAcq1KeuaZADhwtSAAyDL/////HxIFAHRgAwAMAQAAAAAAAAzrS61s+HUX
1618
EADJMv////9OAAAAAAAAALygjcg=
17-
""")
19+
"""
20+
21+
# Miniumum ethernet packet size (60 bytes without FCS)
22+
const PCAP_SMALL = """
23+
Tq98Zs+xVzM8AAAAPAAAAAEAXgBynQDXj6hFQQgARQAA
24+
HrAOQAD9EV7swR1bGOAAcp3yuObOAAr2hcD4AAAAAAAAAAAAAAAAAAAAAA==
25+
"""
26+
27+
# Non-IPv4 packet (ARP protocol)
28+
const PCAP_NONIP = """
29+
8+/iZolNFC1AAAAAQAAAAAD2Y0DDvGQ/XwHjQwgGAAEI
30+
AAYEAAFkP18B40Na4gKMAAAAAAAAWuICgQAAAAAAAAAAAAAAAAAAAAAAAOr6Tyw=
31+
"""
32+
33+
check_detect_fcs(packets; confirm_checksum=true) =
34+
pushfirst!(packets, PCAP_FILE_HEADER) |>
35+
join |>
36+
base64decode |>
37+
PcapBufferReader |>
38+
(x -> try_detect_fcs(x; confirm_checksum))
1839

1940
@testset "pcap_has_fcs" begin
20-
@test pcap_has_fcs(PcapBufferReader(PCAP_FCS))
21-
@test !pcap_has_fcs(PcapBufferReader(PCAP_NOFCS))
41+
@test pcap_has_fcs(PcapBufferReader(base64decode(PCAP_FILE_HEADER * PCAP_FCS)))
42+
@test !pcap_has_fcs(PcapBufferReader(base64decode(PCAP_FILE_HEADER * PCAP_NOFCS)))
43+
end
44+
45+
@testset "try_detect_fcs" begin
46+
@test check_detect_fcs([PCAP_NONIP]) == FCS_UNDETERMINED
47+
@test check_detect_fcs([PCAP_SMALL]) == FCS_UNDETERMINED
48+
@test check_detect_fcs([""]) == FCS_UNDETERMINED # empty pcap
49+
50+
@test check_detect_fcs([PCAP_NONIP, PCAP_SMALL, PCAP_FCS]) == FCS_PRESENT
51+
@test check_detect_fcs([PCAP_NONIP, PCAP_SMALL, PCAP_NOFCS]) == FCS_ABSENT
52+
53+
# Corrupt FCS packets will be ignored by default
54+
@test check_detect_fcs([PCAP_NONIP, PCAP_SMALL, PCAP_CORRUPT_FCS]) == FCS_UNDETERMINED
55+
# But corrupt FCS can be explicitly allowed
56+
@test check_detect_fcs([PCAP_NONIP, PCAP_SMALL, PCAP_CORRUPT_FCS]; confirm_checksum = false) == FCS_PRESENT
57+
58+
# Make sure we skip corrupt frames if confirm_checksum is on
59+
@test check_detect_fcs([PCAP_NONIP, PCAP_SMALL, PCAP_CORRUPT_FCS, PCAP_FCS]) == FCS_PRESENT
60+
@test check_detect_fcs([PCAP_NONIP, PCAP_SMALL, PCAP_CORRUPT_FCS, PCAP_NOFCS]) == FCS_ABSENT
2261
end
2362

2463
@testset "compute_fcs" begin
25-
r_fcs = read(PcapBufferReader(PCAP_FCS))
64+
r_fcs = read(PcapBufferReader(base64decode(PCAP_FILE_HEADER * PCAP_FCS)))
2665
@test check_fcs(r_fcs)
27-
r_no_fcs = read(PcapBufferReader(PCAP_CORRUPT_FCS))
66+
r_no_fcs = read(PcapBufferReader(base64decode(PCAP_FILE_HEADER * PCAP_CORRUPT_FCS)))
2867
@test !check_fcs(r_no_fcs)
2968
end

0 commit comments

Comments
 (0)