Skip to content

Commit 2b4a1c0

Browse files
committed
wip
1 parent 355b7d6 commit 2b4a1c0

36 files changed

Lines changed: 952 additions & 330 deletions

appsec/helper-rust/scripts/generate-sidecar-ffi.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ docker run --init --rm \
2020
--mount type=volume,src=php-tracer-cargo-cache-git,dst=/usr/local/cargo/git \
2121
datadog/dd-appsec-php-ci:php-deps \
2222
-e -c "
23-
command -v bindgen >/dev/null 2>&1 || cargo install bindgen-cli -q
23+
command -v bindgen >/dev/null 2>&1 || cargo install bindgen-cli --locked -q
2424
cd $COMPONENTS_RS_INNER
2525
bindgen sidecar.h \
2626
--allowlist-function 'ddog_sidecar_appsec_register_message_handler' \

appsec/helper-rust/src/client.rs

Lines changed: 65 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -96,44 +96,47 @@ pub struct Client {
9696
service: Option<TrackedService>,
9797
sidecar_settings: Option<protocol::SidecarSettings>,
9898
metrics_last_registered: Cell<Option<Instant>>,
99-
req_receiver: Option<mpsc::Receiver<HelperRequest>>,
100-
req_sender: mpsc::Sender<HelperRequest>,
10199
}
102100

103101
static CLIENT_SERIAL: AtomicU64 = AtomicU64::new(1);
104102
impl Client {
105103
pub fn new(service_manager: &'static ServiceManager) -> Self {
106-
let (tx, rx) = mpsc::channel(5);
107104
Self {
108105
id: CLIENT_SERIAL.fetch_add(1, atomic::Ordering::Relaxed),
109106
service_manager,
110107
service: None,
111108
sidecar_settings: None,
112109
metrics_last_registered: Default::default(),
113-
req_receiver: Some(rx),
114-
req_sender: tx,
115110
}
116111
}
117112

118-
pub async fn entrypoint(self, cancel_token: CancellationToken) {
113+
pub async fn entrypoint(
114+
self,
115+
rx: mpsc::Receiver<HelperRequest>,
116+
cancel_token: CancellationToken,
117+
) {
119118
// wrap entrypoint with the task locals that allow:
120119
// - client id in the logs
121120
// - submission of errors to telemetry (if they happen after client_init)
122121
let client_id = self.id;
123-
let entrypoint_fut = self.do_entrypoint(cancel_token);
122+
let entrypoint_fut = self.do_entrypoint(rx, cancel_token);
124123
log::with_scoped_client_id(client_id, with_error_telemetry_handle(entrypoint_fut)).await;
125124
}
126125

127-
async fn do_entrypoint(mut self, cancel_token: CancellationToken) {
126+
async fn do_entrypoint(
127+
self,
128+
rx: mpsc::Receiver<HelperRequest>,
129+
cancel_token: CancellationToken,
130+
) {
128131
info!("starting");
129132

130-
let res = do_client_entrypoint(&mut self, cancel_token).await;
133+
let res = do_client_entrypoint(self, rx, cancel_token).await;
131134
match res {
132135
Ok(_) => {
133136
info!("ended normally");
134137
}
135138
Err(err) if err.is::<ForcefulDisconnect>() => {
136-
warn!("ended due to client connectivity issue: {}", err);
139+
info!("ended due to client connectivity issue: {:#}", err);
137140
}
138141
Err(err) => {
139142
error!("ended with failure: {:#}", err);
@@ -145,53 +148,21 @@ impl Client {
145148
pub fn get_service(&self) -> &Service {
146149
self.service.as_ref().expect("service not initialized")
147150
}
148-
149-
pub fn get_req_sender(&self) -> mpsc::Sender<HelperRequest> {
150-
self.req_sender.clone()
151-
}
152-
153-
fn req_stream(&mut self) -> CommandStream {
154-
let receiver = std::mem::take(&mut self.req_receiver).unwrap();
155-
let cmd_stream = ReceiverStream::new(receiver);
156-
let cmd_stream = StreamExt::map(cmd_stream, |msg| {
157-
let mut codec = protocol::CommandCodec;
158-
(
159-
codec.decode_eof(&mut BytesMut::from(msg.command)),
160-
msg.response_tx,
161-
)
162-
});
163-
let cmd_stream = cmd_stream
164-
.take_while(|r| matches!(r, (Ok(Some(_)), _) | (Err(_), _)))
165-
.map(|r| match r {
166-
(Ok(Some(cmd)), response_tx) => Ok((cmd, response_tx)),
167-
(Err(e), response_tx) => {
168-
let fatal_error = FatalRequestError(
169-
anyhow::Error::new(e).context("Error decoding command"),
170-
response_tx,
171-
);
172-
Err(fatal_error)
173-
}
174-
(Ok(None), _) => unreachable!(),
175-
});
176-
Box::pin(cmd_stream)
177-
}
178151
}
179152

180-
/// Indicates a clean client shutdown - the client properly closed its connection
181-
/// after completing all pending writes. This is NOT an error condition.
153+
/// Indicates a clean client shutdown - happened after good bye.
182154
#[derive(Debug, Error)]
183155
#[error("Client closed connection cleanly")]
184156
struct CleanShutdown;
185157

186158
/// Indicates the client disconnected unexpectedly (no client_shutdown received,
187-
/// or client_shutdown reported an unclean exit). Reported as a connectivity
188-
/// issue, not a protocol error.
159+
/// or client_shutdown reported an unclean exit).
189160
#[derive(Debug, Error)]
190161
#[error("client disconnected forcefully: {0}")]
191162
struct ForcefulDisconnect(String);
192163

193-
/// A fatal error occurred while processing a request. The client will be sent
194-
/// a FatalError response and the connection will be closed.
164+
/// A fatal error occurred while processing a request. The extension will be
165+
/// sent a FatalError response and the client will be abandoned.
195166
#[derive(Error)]
196167
#[error("{0}")]
197168
struct FatalRequestError(anyhow::Error, oneshot::Sender<sidecar_msg::HelperResponse>);
@@ -203,15 +174,16 @@ impl fmt::Debug for FatalRequestError {
203174
}
204175

205176
async fn do_client_entrypoint(
206-
client: &mut Client,
177+
mut client: Client,
178+
rx: mpsc::Receiver<HelperRequest>,
207179
cancel_token: CancellationToken,
208180
) -> anyhow::Result<()> {
209-
let mut cmd_stream = client.req_stream();
181+
let mut cmd_stream = make_command_stream(rx);
210182

211183
// first, client_init
212184
match recv_command(&mut cmd_stream, &cancel_token).await {
213185
Ok((protocol::Command::ClientInit(args), response_tx)) => {
214-
let resp = handle_client_init(client, *args);
186+
let resp = handle_client_init(&mut client, *args);
215187
match resp {
216188
Ok(resp) => {
217189
send_command_resp(response_tx, resp)?;
@@ -229,26 +201,10 @@ async fn do_client_entrypoint(
229201
}
230202
}
231203
}
232-
Ok((protocol::Command::ClientShutdown(args), response_tx)) => {
233-
let res = handle_client_shutdown(response_tx, *args);
234-
return if res
235-
.as_ref()
236-
.err()
237-
.map_or(false, |e| e.is::<CleanShutdown>())
238-
{
239-
Ok(())
240-
} else {
241-
res
242-
};
243-
}
244204
Ok((cmd, response_tx)) => {
245205
send_command_resp(response_tx, CommandResponse::FatalError)?;
246206
anyhow::bail!("expected client_init, got {:?}", cmd);
247207
}
248-
Err(e) if e.is::<CleanShutdown>() => {
249-
info!("client session was dropped");
250-
return Ok(());
251-
}
252208
Err(e) if e.is::<FatalRequestError>() => {
253209
let FatalRequestError(inner_err, response_tx) = e
254210
.downcast::<FatalRequestError>()
@@ -263,7 +219,7 @@ async fn do_client_entrypoint(
263219

264220
// then the request loop
265221
loop {
266-
match do_request_loop_iter(client, &mut cmd_stream, &cancel_token).await {
222+
match do_request_loop_iter(&mut client, &mut cmd_stream, &cancel_token).await {
267223
Ok(_) => {
268224
debug!("request done; waiting for new one");
269225
}
@@ -288,6 +244,31 @@ async fn do_client_entrypoint(
288244
}
289245
}
290246

247+
fn make_command_stream(rx: mpsc::Receiver<HelperRequest>) -> CommandStream {
248+
let cmd_stream = ReceiverStream::new(rx);
249+
let cmd_stream = StreamExt::map(cmd_stream, |msg| {
250+
let mut codec = protocol::CommandCodec;
251+
(
252+
codec.decode_eof(&mut BytesMut::from(msg.command)),
253+
msg.response_tx,
254+
)
255+
});
256+
let cmd_stream = cmd_stream
257+
.take_while(|r| matches!(r, (Ok(Some(_)), _) | (Err(_), _)))
258+
.map(|r| match r {
259+
(Ok(Some(cmd)), response_tx) => Ok((cmd, response_tx)),
260+
(Err(e), response_tx) => {
261+
let fatal_error = FatalRequestError(
262+
anyhow::Error::new(e).context("Error decoding command"),
263+
response_tx,
264+
);
265+
Err(fatal_error)
266+
}
267+
(Ok(None), _) => unreachable!(),
268+
});
269+
Box::pin(cmd_stream)
270+
}
271+
291272
fn handle_client_init(
292273
client: &mut Client,
293274
args: protocol::ClientInitArgs,
@@ -420,7 +401,8 @@ async fn do_request_loop_iter(
420401
cmd_stream: &mut CommandStream,
421402
cancel_token: &CancellationToken,
422403
) -> anyhow::Result<()> {
423-
// wait for any number of config_syncs, followed by request_init
404+
// wait for any number of config_syncs, followed by request_init.
405+
// client_shutdown may be gotten at any time
424406
let mut req_ctx = match recv_command(cmd_stream, cancel_token).await? {
425407
(protocol::Command::ClientShutdown(args), response_tx) => {
426408
return handle_client_shutdown(response_tx, *args);
@@ -758,7 +740,7 @@ impl ReqContext {
758740
.waf_subctxs
759741
.remove(subctx_id)
760742
.or_else(|| self.waf_ctx.new_subcontext().ok()) // error should not happen
761-
.ok_or(anyhow!("Failed to create subcontext"))?;
743+
.ok_or_else(|| anyhow!("Failed to create subcontext"))?;
762744
Ok(RunnableCtx::Owned(subctx))
763745
} else {
764746
let waf_ctx = &mut self.waf_ctx;
@@ -873,7 +855,7 @@ fn convert_actions(
873855
let parameters = kv
874856
.value()
875857
.as_type::<libddwaf::object::WafMap>()
876-
.ok_or(anyhow!("Action parameter map not a map"))?
858+
.ok_or_else(|| anyhow!("Action parameter map not a map"))?
877859
.iter()
878860
.try_fold(HashMap::new(), |mut acc, kv| -> anyhow::Result<_> {
879861
let key = kv.key_str().map_err(|e| anyhow!(e.to_string()))?;
@@ -1335,12 +1317,8 @@ mod tests {
13351317
Box::leak(bytes.into_boxed_slice())
13361318
}
13371319

1338-
fn make_test_client() -> (Client, mpsc::Sender<HelperRequest>) {
1339-
let service_manager: &'static ServiceManager =
1340-
Box::leak(Box::new(ServiceManager::new()));
1341-
let client = Client::new(service_manager);
1342-
let sender = client.get_req_sender();
1343-
(client, sender)
1320+
fn make_channel() -> (mpsc::Sender<HelperRequest>, mpsc::Receiver<HelperRequest>) {
1321+
mpsc::channel(5)
13441322
}
13451323

13461324
fn enqueue_messages(tx: &mpsc::Sender<HelperRequest>, messages: Vec<Vec<u8>>) {
@@ -1354,18 +1332,12 @@ mod tests {
13541332
}
13551333
}
13561334

1357-
fn close_request_channel(client: &mut Client, tx: mpsc::Sender<HelperRequest>) {
1358-
let (dummy_tx, _dummy_rx) = mpsc::channel(1);
1359-
client.req_sender = dummy_tx;
1360-
drop(tx);
1361-
}
1362-
13631335
/// Bare EOF without a prior client_shutdown is treated as a forceful disconnect
13641336
#[tokio::test]
13651337
async fn test_eof_without_goodbye_returns_forceful_disconnect() {
1366-
let (mut client, tx) = make_test_client();
1367-
close_request_channel(&mut client, tx);
1368-
let mut stream = client.req_stream();
1338+
let (tx, rx) = make_channel();
1339+
drop(tx);
1340+
let mut stream = make_command_stream(rx);
13691341
let cancel_token = CancellationToken::new();
13701342
let err = match recv_command(&mut stream, &cancel_token).await {
13711343
Ok(_) => panic!("expected forceful disconnect error"),
@@ -1381,10 +1353,10 @@ mod tests {
13811353
/// Incomplete data should return a fatal request decode error
13821354
#[tokio::test]
13831355
async fn test_incomplete_data_returns_fatal_request_error() {
1384-
let (mut client, tx) = make_test_client();
1356+
let (tx, rx) = make_channel();
13851357
enqueue_messages(&tx, vec![b"dds".to_vec()]);
1386-
close_request_channel(&mut client, tx);
1387-
let mut stream = client.req_stream();
1358+
drop(tx);
1359+
let mut stream = make_command_stream(rx);
13881360
let cancel_token = CancellationToken::new();
13891361
let err = match recv_command(&mut stream, &cancel_token).await {
13901362
Ok(_) => panic!("expected decode failure"),
@@ -1405,10 +1377,10 @@ mod tests {
14051377
invalid.extend_from_slice(&10u32.to_le_bytes());
14061378
invalid.extend_from_slice(b"0123456789");
14071379

1408-
let (mut client, tx) = make_test_client();
1380+
let (tx, rx) = make_channel();
14091381
enqueue_messages(&tx, vec![invalid]);
1410-
close_request_channel(&mut client, tx);
1411-
let mut stream = client.req_stream();
1382+
drop(tx);
1383+
let mut stream = make_command_stream(rx);
14121384
let cancel_token = CancellationToken::new();
14131385
let err = match recv_command(&mut stream, &cancel_token).await {
14141386
Ok(_) => panic!("expected invalid header error"),
@@ -1421,56 +1393,16 @@ mod tests {
14211393
);
14221394
}
14231395

1424-
/// client_shutdown with C wire format (num_args=1 wraps the map in a 1-element array)
1425-
/// decodes correctly. Without this test the protocol mismatch is silent.
1426-
#[tokio::test]
1427-
async fn test_client_shutdown_c_wire_format_decodes() {
1428-
use rmp_serde::Serializer;
1429-
use serde::Serialize;
1430-
// C side produces: ("client_shutdown", ({clean: true, error: nil},))
1431-
// The outer tuple's second element is a 1-element tuple (array) wrapping the map.
1432-
#[derive(serde::Serialize)]
1433-
struct Inner {
1434-
clean: bool,
1435-
error: Option<String>,
1436-
}
1437-
let msg = (
1438-
"client_shutdown",
1439-
(Inner {
1440-
clean: true,
1441-
error: None,
1442-
},),
1443-
);
1444-
let mut body = Vec::new();
1445-
msg.serialize(&mut Serializer::new(&mut body)).unwrap();
1446-
let framed = serialize_message_raw(body);
1447-
1448-
let (mut client, tx) = make_test_client();
1449-
enqueue_messages(&tx, vec![framed]);
1450-
drop(tx);
1451-
let mut stream = client.req_stream();
1452-
let cancel_token = CancellationToken::new();
1453-
let (cmd, _response_tx) = recv_command(&mut stream, &cancel_token)
1454-
.await
1455-
.expect("should decode client_shutdown in C wire format");
1456-
assert!(
1457-
matches!(cmd, protocol::Command::ClientShutdown(_)),
1458-
"should decode as ClientShutdown, got {:?}",
1459-
cmd
1460-
);
1461-
}
1462-
14631396
/// Cancellation should return CleanShutdown (treated same as clean close)
14641397
#[tokio::test]
14651398
async fn test_cancellation_returns_clean_shutdown() {
1466-
let (mut client, tx) = make_test_client();
1467-
let mut stream = client.req_stream();
1399+
let (_tx, rx) = make_channel();
1400+
let mut stream = make_command_stream(rx);
14681401
let cancel_token = CancellationToken::new();
14691402

14701403
cancel_token.cancel();
14711404

1472-
drop(tx);
1473-
1405+
// _tx stays alive so only the cancel branch is immediately ready in select!
14741406
let err = match recv_command(&mut stream, &cancel_token).await {
14751407
Ok(_) => panic!("expected cancellation as clean shutdown"),
14761408
Err(err) => err,

appsec/helper-rust/src/client/log.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ pub fn log_error_with_backtrace_at(
3333
bt: &'a Backtrace,
3434
}
3535

36-
impl<'kvs> log::kv::Source for BacktraceKvs<'kvs> {
36+
impl log::kv::Source for BacktraceKvs<'_> {
3737
fn visit<'a>(
3838
&'a self,
3939
visitor: &mut dyn log::kv::VisitSource<'a>,
4040
) -> Result<(), log::kv::Error> {
4141
visitor.visit_pair(
4242
log::kv::Key::from_str(ANYHOW_BACKTRACE_KEY),
43-
log::kv::Value::from_display(self.bt),
43+
log::kv::Value::from_display(&self.bt),
4444
)
4545
}
4646
}
@@ -81,7 +81,11 @@ pub trait TryGetBacktrace {
8181
impl TryGetBacktrace for anyhow::Error {
8282
#[inline]
8383
fn try_get_backtrace(&self) -> Option<&Backtrace> {
84-
Some(self.backtrace())
84+
let bt = self.backtrace();
85+
match bt.status() {
86+
std::backtrace::BacktraceStatus::Captured => Some(bt),
87+
_ => None,
88+
}
8589
}
8690
}
8791

0 commit comments

Comments
 (0)