srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_icmp.cpp
blob: 7016a72812d52df02dece988de7cadf6903293ad (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#include <doctest/doctest.h>

#include <vector>

#include "packeteer/net/icmp.hpp"

using namespace packeteer::net;

TEST_CASE("parse_icmpv4 decodes an echo request with identifier/sequence") {
    std::vector<unsigned char> bytes = {8, 0, 0x00, 0x00, 0x1c, 0x05, 0x00, 0x01};
    auto icmp = parse_icmpv4(bytes);
    REQUIRE(icmp.has_value());
    CHECK(icmp->type == 8);
    CHECK(icmp->code == 0);
    REQUIRE(icmp->identifier.has_value());
    CHECK(*icmp->identifier == 0x1c05);
    REQUIRE(icmp->sequence.has_value());
    CHECK(*icmp->sequence == 1);
}

TEST_CASE("parse_icmpv4 decodes an echo reply the same way as a request") {
    std::vector<unsigned char> bytes = {0, 0, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02};
    auto icmp = parse_icmpv4(bytes);
    REQUIRE(icmp.has_value());
    CHECK(icmp->type == 0);
    REQUIRE(icmp->identifier.has_value());
    CHECK(*icmp->identifier == 1);
}

TEST_CASE("parse_icmpv4 decodes a non-echo type without an identifier/sequence") {
    std::vector<unsigned char> bytes = {3, 1, 0x00, 0x00};  // dest unreachable, host unreachable
    auto icmp = parse_icmpv4(bytes);
    REQUIRE(icmp.has_value());
    CHECK(icmp->type == 3);
    CHECK(icmp->code == 1);
    CHECK_FALSE(icmp->identifier.has_value());
}

TEST_CASE("parse_icmpv4 rejects a buffer shorter than the fixed header") {
    std::vector<unsigned char> bytes(3, 0);
    CHECK_FALSE(parse_icmpv4(bytes).has_value());
}

TEST_CASE("icmpv4_type_name covers known types and falls back for unknown ones") {
    CHECK(icmpv4_type_name(8) == "Echo Request");
    CHECK(icmpv4_type_name(0) == "Echo Reply");
    CHECK(icmpv4_type_name(3) == "Destination Unreachable");
    CHECK(icmpv4_type_name(200) == "type=200");
}

TEST_CASE("parse_icmpv6 decodes an echo request with identifier/sequence") {
    std::vector<unsigned char> bytes = {128, 0, 0x00, 0x00, 0x1c, 0x05, 0x00, 0x01};
    auto icmp = parse_icmpv6(bytes);
    REQUIRE(icmp.has_value());
    CHECK(icmp->type == 128);
    REQUIRE(icmp->identifier.has_value());
    CHECK(*icmp->identifier == 0x1c05);
}

TEST_CASE("parse_icmpv6 decodes a non-echo type (e.g. Neighbor Solicitation) without id/seq") {
    std::vector<unsigned char> bytes = {135, 0, 0x00, 0x00};
    auto icmp = parse_icmpv6(bytes);
    REQUIRE(icmp.has_value());
    CHECK(icmp->type == 135);
    CHECK_FALSE(icmp->identifier.has_value());
}

TEST_CASE("icmpv6_type_name covers known types and falls back for unknown ones") {
    CHECK(icmpv6_type_name(128) == "Echo Request");
    CHECK(icmpv6_type_name(135) == "Neighbor Solicitation");
    CHECK(icmpv6_type_name(134) == "Router Advertisement");
    CHECK(icmpv6_type_name(250) == "type=250");
}

namespace {

// Builds a real ICMPv4 Destination Unreachable message wrapping a
// truncated original UDP packet - exactly the shape RFC 792 promises:
// original IP header + first 8 bytes of the original datagram's data
// (enough to reach a UDP/TCP header's two port fields).
std::vector<unsigned char> dest_unreachable_wrapping_udp() {
    std::vector<unsigned char> original_ip(20, 0);
    original_ip[0] = 0x45;
    original_ip[9] = kProtoUdp;
    original_ip[12] = 10; original_ip[13] = 0; original_ip[14] = 0; original_ip[15] = 5;
    original_ip[16] = 10; original_ip[17] = 0; original_ip[18] = 0; original_ip[19] = 6;

    std::vector<unsigned char> original_udp_start = {0x1F, 0x90, 0x00, 0x35};  // src=8080, dst=53

    std::vector<unsigned char> icmp = {3, 3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};  // port unreachable
    icmp.insert(icmp.end(), original_ip.begin(), original_ip.end());
    icmp.insert(icmp.end(), original_udp_start.begin(), original_udp_start.end());
    return icmp;
}

}  // namespace

TEST_CASE("icmpv4_embedded_flow recovers the original flow's addresses and ports") {
    auto flow = icmpv4_embedded_flow(dest_unreachable_wrapping_udp());
    REQUIRE(flow.has_value());
    CHECK(*flow == "10.0.0.5 -> 10.0.0.6 proto=17 (8080 -> 53)");
}

TEST_CASE("icmpv4_embedded_flow omits ports for a non-TCP/UDP embedded protocol") {
    std::vector<unsigned char> original_ip(20, 0);
    original_ip[0] = 0x45;
    original_ip[9] = kProtoIcmp;  // an ICMP error about an ICMP packet (e.g. a ping that failed)
    original_ip[12] = 10; original_ip[15] = 1;
    original_ip[16] = 10; original_ip[19] = 2;

    std::vector<unsigned char> icmp = {11, 0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    icmp.insert(icmp.end(), original_ip.begin(), original_ip.end());

    auto flow = icmpv4_embedded_flow(icmp);
    REQUIRE(flow.has_value());
    CHECK(*flow == "10.0.0.1 -> 10.0.0.2 proto=1");
}

TEST_CASE("icmpv4_embedded_flow returns nullopt when there's no room for an embedded packet") {
    std::vector<unsigned char> icmp = {3, 3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    CHECK_FALSE(icmpv4_embedded_flow(icmp).has_value());
}

TEST_CASE("icmpv4_is_error_type covers the RFC 792 error types and excludes echo/timestamp") {
    CHECK(icmpv4_is_error_type(3));   // Destination Unreachable
    CHECK(icmpv4_is_error_type(11));  // Time Exceeded
    CHECK_FALSE(icmpv4_is_error_type(8));   // Echo Request
    CHECK_FALSE(icmpv4_is_error_type(13));  // Timestamp Request
}

TEST_CASE("icmpv6_embedded_flow recovers the original flow's addresses and ports") {
    std::vector<unsigned char> original_ip6(40, 0);
    original_ip6[0] = 0x60;
    original_ip6[6] = kProtoUdp;  // next_header
    original_ip6[7] = 64;         // hop_limit
    original_ip6[8] = 0x20; original_ip6[9] = 0x01;  // src: 2001:db8::1
    original_ip6[10] = 0x0d; original_ip6[11] = 0xb8;
    original_ip6[23] = 0x01;
    original_ip6[24] = 0x20; original_ip6[25] = 0x01;  // dst: 2001:db8::2
    original_ip6[26] = 0x0d; original_ip6[27] = 0xb8;
    original_ip6[39] = 0x02;

    std::vector<unsigned char> original_udp_start = {0x1F, 0x90, 0x00, 0x35};

    std::vector<unsigned char> icmp = {1, 4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};  // port unreachable
    icmp.insert(icmp.end(), original_ip6.begin(), original_ip6.end());
    icmp.insert(icmp.end(), original_udp_start.begin(), original_udp_start.end());

    auto flow = icmpv6_embedded_flow(icmp);
    REQUIRE(flow.has_value());
    CHECK(*flow == "2001:db8::1 -> 2001:db8::2 next=17 (8080 -> 53)");
}

TEST_CASE("icmpv6_is_error_type covers the RFC 4443 error types and excludes echo/Redirect") {
    CHECK(icmpv6_is_error_type(1));  // Destination Unreachable
    CHECK(icmpv6_is_error_type(3));  // Time Exceeded
    CHECK_FALSE(icmpv6_is_error_type(128));  // Echo Request
    CHECK_FALSE(icmpv6_is_error_type(137));  // Redirect: different, not generic embedded-packet format
}

TEST_CASE("the same type number means something different in each protocol's table") {
    // The whole reason these are two separate tables, not one shared by
    // number: ICMPv4's echo request is type 8, but ICMPv6's type 8
    // isn't in its table at all (echo request is 128 there instead).
    CHECK(icmpv4_type_name(8) == "Echo Request");
    CHECK(icmpv6_type_name(8) == "type=8");
    // And type 4 means "Parameter Problem" in ICMPv6 but is unmapped
    // (falls back) in the ICMPv4 table.
    CHECK(icmpv6_type_name(4) == "Parameter Problem");
    CHECK(icmpv4_type_name(4) == "Source Quench");
}