Skip to content

Commit 1f6d18b

Browse files
authored
Add TLS protocol stitcher (#2058)
Summary: Add TLS protocol stitcher This is meant to be a relatively bare bones stitcher at first. It will be later extended but I wanted to get the minimal viable version in first. My plan is to revisit this and other parser enhancements as an excuse to "build in public" and video record hacking on Pixie's socket tracer. Relevant Issues: N/A Type of change: /kind feature Test Plan: Verified the following - [x] New stitcher tests pass - [x] Stitcher is functional with the rest of the TLS protocol changes in place ![tls](https://github.com/user-attachments/assets/4c98dfae-0c9b-4c8a-a524-9ff3ffb3ec45) Signed-off-by: Dom Del Nano <ddelnano@gmail.com>
1 parent 685ed42 commit 1f6d18b

File tree

4 files changed

+313
-0
lines changed

4 files changed

+313
-0
lines changed

src/stirling/source_connectors/socket_tracer/protocols/tls/BUILD.bazel

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,9 @@ pl_cc_test(
4545
srcs = ["parse_test.cc"],
4646
deps = [":cc_library"],
4747
)
48+
49+
pl_cc_test(
50+
name = "stitcher_test",
51+
srcs = ["stitcher_test.cc"],
52+
deps = [":cc_library"],
53+
)
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Copyright 2018- The Pixie Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
*/
18+
19+
#include "src/stirling/source_connectors/socket_tracer/protocols/tls/stitcher.h"
20+
21+
#include <utility>
22+
23+
#include "src/stirling/source_connectors/socket_tracer/protocols/common/interface.h"
24+
#include "src/stirling/source_connectors/socket_tracer/protocols/tls/parse.h"
25+
#include "src/stirling/source_connectors/socket_tracer/protocols/tls/types.h"
26+
27+
namespace px {
28+
namespace stirling {
29+
namespace protocols {
30+
namespace tls {
31+
32+
/* The TLS protocol is an in-order protocol. The handshake protocol specificially must
33+
* be sent in the expected order or it results in a fatal protocol error.
34+
*
35+
* TLS v1.2 handeshake. Taken from RFC5246 page 35
36+
*
37+
* Client Server
38+
*
39+
* ClientHello -------->
40+
* ServerHello
41+
* Certificate*
42+
* ServerKeyExchange*
43+
* CertificateRequest*
44+
* <-------- ServerHelloDone
45+
* Certificate*
46+
* ClientKeyExchange
47+
* CertificateVerify*
48+
* [ChangeCipherSpec]
49+
* Finished -------->
50+
* [ChangeCipherSpec]
51+
* <-------- Finished
52+
* Application Data <-------> Application Data
53+
*
54+
* `*` Indicates optional or situation-dependent messages that are not
55+
* always sent.
56+
*
57+
* TLS v1.3 handshake. Taken from RFC8446 page 10
58+
*
59+
* Client Server
60+
*
61+
* Key ^ ClientHello
62+
* Exch | + key_share*
63+
* | + signature_algorithms*
64+
* | + psk_key_exchange_modes*
65+
* v + pre_shared_key* -------->
66+
* ServerHello ^ Key
67+
* + key_share* | Exch
68+
* + pre_shared_key* v
69+
* {EncryptedExtensions} ^ Server
70+
* {CertificateRequest*} v Params
71+
* {Certificate*} ^
72+
* {CertificateVerify*} | Auth
73+
* {Finished} v
74+
* <-------- [Application Data*]
75+
* ^ {Certificate*}
76+
* Auth | {CertificateVerify*}
77+
* v {Finished} -------->
78+
* [Application Data] <-------> [Application Data]
79+
*
80+
* + Indicates noteworthy extensions sent in the
81+
* previously noted message.
82+
*
83+
* * Indicates optional or situation-dependent
84+
* messages/extensions that are not always sent.
85+
*
86+
* {} Indicates messages protected using keys
87+
* derived from a [sender]_handshake_traffic_secret.
88+
*
89+
* [] Indicates messages protected using keys
90+
* derived from [sender]_application_traffic_secret_N.
91+
*/
92+
RecordsWithErrorCount<tls::Record> StitchFrames(std::deque<tls::Frame>* reqs,
93+
std::deque<tls::Frame>* resps) {
94+
std::vector<tls::Record> records;
95+
int error_count = 0;
96+
uint64_t latest_resp_ts = 0;
97+
98+
for (auto& resp : *resps) {
99+
if (resp.timestamp_ns > latest_resp_ts) {
100+
latest_resp_ts = resp.timestamp_ns;
101+
}
102+
103+
for (auto req_it = reqs->begin(); req_it != reqs->end(); req_it++) {
104+
auto& req = *req_it;
105+
106+
if (req.consumed) continue;
107+
108+
// For now only kHandshakes matter since that transmits the interesting data.
109+
// We will probably wnat to support kAlert as well.
110+
if (req.content_type == ContentType::kApplicationData ||
111+
req.content_type == ContentType::kChangeCipherSpec ||
112+
req.content_type == ContentType::kAlert || req.content_type == ContentType::kHeartbeat) {
113+
req.consumed = true;
114+
continue;
115+
}
116+
117+
if (req.content_type == ContentType::kHandshake &&
118+
req.handshake_type == HandshakeType::kClientHello &&
119+
resp.content_type == ContentType::kHandshake &&
120+
resp.handshake_type == HandshakeType::kServerHello) {
121+
req.consumed = true;
122+
resp.consumed = true;
123+
records.push_back({std::move(req), std::move(resp)});
124+
break;
125+
}
126+
}
127+
}
128+
129+
auto erase_until_iter = reqs->begin();
130+
while (erase_until_iter != reqs->end() &&
131+
(erase_until_iter->consumed || erase_until_iter->timestamp_ns < latest_resp_ts)) {
132+
if (!erase_until_iter->consumed) {
133+
error_count++;
134+
}
135+
erase_until_iter++;
136+
}
137+
reqs->erase(reqs->begin(), erase_until_iter);
138+
resps->clear();
139+
return {records, error_count};
140+
}
141+
142+
} // namespace tls
143+
} // namespace protocols
144+
} // namespace stirling
145+
} // namespace px
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2018- The Pixie Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
*/
18+
19+
#pragma once
20+
21+
#include <deque>
22+
#include <vector>
23+
24+
#include "src/common/base/base.h"
25+
#include "src/stirling/source_connectors/socket_tracer/protocols/common/interface.h"
26+
#include "src/stirling/source_connectors/socket_tracer/protocols/tls/types.h"
27+
28+
namespace px {
29+
namespace stirling {
30+
namespace protocols {
31+
namespace tls {
32+
33+
RecordsWithErrorCount<tls::Record> StitchFrames(std::deque<tls::Frame>* reqs,
34+
std::deque<tls::Frame>* resps);
35+
} // namespace tls
36+
37+
template <>
38+
inline RecordsWithErrorCount<tls::Record> StitchFrames(std::deque<tls::Frame>* reqs,
39+
std::deque<tls::Frame>* resps,
40+
NoState* /*state*/) {
41+
return tls::StitchFrames(reqs, resps);
42+
}
43+
44+
} // namespace protocols
45+
} // namespace stirling
46+
} // namespace px
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright 2018- The Pixie Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
*/
18+
19+
#include "src/stirling/source_connectors/socket_tracer/protocols/tls/stitcher.h"
20+
21+
#include <string>
22+
23+
#include "src/common/testing/testing.h"
24+
#include "src/stirling/source_connectors/socket_tracer/protocols/tls/parse.h"
25+
26+
namespace px {
27+
namespace stirling {
28+
namespace protocols {
29+
30+
using ::testing::ElementsAre;
31+
using ::testing::ElementsAreArray;
32+
using ::testing::Field;
33+
using ::testing::IsEmpty;
34+
using ::testing::SizeIs;
35+
using ::testing::StrEq;
36+
37+
class StitchFramesTest : public ::testing::Test {};
38+
39+
tls::Frame CreateTLSFrame(uint64_t ts_ns, tls::ContentType content_type,
40+
tls::LegacyVersion legacy_version, tls::HandshakeType handshake_type,
41+
std::string session_id) {
42+
tls::Frame frame;
43+
frame.timestamp_ns = ts_ns;
44+
frame.content_type = content_type;
45+
frame.legacy_version = legacy_version;
46+
frame.handshake_type = handshake_type;
47+
frame.session_id = session_id;
48+
return frame;
49+
}
50+
51+
tls::Frame CreateNonHandshakeFrame(uint64_t ts_ns, tls::ContentType content_type,
52+
tls::LegacyVersion legacy_version) {
53+
tls::Frame frame;
54+
frame.timestamp_ns = ts_ns;
55+
frame.content_type = content_type;
56+
frame.legacy_version = legacy_version;
57+
return frame;
58+
}
59+
60+
TEST_F(StitchFramesTest, HandlesTLS1_3Handshake) {
61+
std::deque<tls::Frame> reqs = {
62+
CreateTLSFrame(0, tls::ContentType::kHandshake, tls::LegacyVersion::kTLS1_0,
63+
tls::HandshakeType::kClientHello, "session_id"),
64+
};
65+
std::deque<tls::Frame> resps = {
66+
CreateTLSFrame(1, tls::ContentType::kHandshake, tls::LegacyVersion::kTLS1_2,
67+
tls::HandshakeType::kServerHello, "session_id"),
68+
};
69+
RecordsWithErrorCount<tls::Record> result = tls::StitchFrames(&reqs, &resps);
70+
EXPECT_EQ(result.error_count, 0);
71+
EXPECT_EQ(result.records.size(), 1);
72+
EXPECT_THAT(reqs, IsEmpty());
73+
EXPECT_THAT(resps, IsEmpty());
74+
}
75+
76+
TEST_F(StitchFramesTest, HandlesApplicationDataAndChangeCipherSpecWithFullHandshake) {
77+
std::deque<tls::Frame> reqs = {
78+
CreateTLSFrame(0, tls::ContentType::kHandshake, tls::LegacyVersion::kTLS1_0,
79+
tls::HandshakeType::kClientHello, "session_id"),
80+
CreateNonHandshakeFrame(2, tls::ContentType::kChangeCipherSpec, tls::LegacyVersion::kTLS1_2),
81+
CreateNonHandshakeFrame(3, tls::ContentType::kApplicationData, tls::LegacyVersion::kTLS1_2),
82+
CreateNonHandshakeFrame(4, tls::ContentType::kApplicationData, tls::LegacyVersion::kTLS1_2),
83+
};
84+
std::deque<tls::Frame> resps = {
85+
CreateTLSFrame(1, tls::ContentType::kHandshake, tls::LegacyVersion::kTLS1_2,
86+
tls::HandshakeType::kServerHello, "session_id"),
87+
CreateNonHandshakeFrame(3, tls::ContentType::kChangeCipherSpec, tls::LegacyVersion::kTLS1_2),
88+
CreateNonHandshakeFrame(5, tls::ContentType::kApplicationData, tls::LegacyVersion::kTLS1_2),
89+
};
90+
RecordsWithErrorCount<tls::Record> result = tls::StitchFrames(&reqs, &resps);
91+
EXPECT_EQ(result.error_count, 0);
92+
EXPECT_EQ(result.records.size(), 1);
93+
EXPECT_THAT(reqs, IsEmpty());
94+
EXPECT_THAT(resps, IsEmpty());
95+
}
96+
97+
TEST_F(StitchFramesTest, WaitsToProcessReqFrameUntilRespFrameIsReceived) {
98+
std::deque<tls::Frame> reqs = {
99+
CreateTLSFrame(0, tls::ContentType::kHandshake, tls::LegacyVersion::kTLS1_0,
100+
tls::HandshakeType::kClientHello, "session_id"),
101+
CreateNonHandshakeFrame(2, tls::ContentType::kChangeCipherSpec, tls::LegacyVersion::kTLS1_2),
102+
};
103+
std::deque<tls::Frame> resps = {
104+
CreateTLSFrame(1, tls::ContentType::kHandshake, tls::LegacyVersion::kTLS1_2,
105+
tls::HandshakeType::kServerHello, "session_id"),
106+
};
107+
RecordsWithErrorCount<tls::Record> result = tls::StitchFrames(&reqs, &resps);
108+
EXPECT_EQ(result.error_count, 0);
109+
EXPECT_EQ(result.records.size(), 1);
110+
EXPECT_THAT(reqs, SizeIs(1));
111+
EXPECT_THAT(resps, IsEmpty());
112+
}
113+
114+
} // namespace protocols
115+
} // namespace stirling
116+
} // namespace px

0 commit comments

Comments
 (0)