Skip to content

Commit 4a3621f

Browse files
vjardinrjarry
authored andcommitted
api: add file descriptor passing over the UNIX API socket
The upcoming packet capture feature needs to share a memfd between the grout daemon and its clients. Extend the API protocol to pass a file descriptor alongside the response payload using SCM_RIGHTS ancillary data on the UNIX socket. On the server side, api_out_fd() sets an fd in struct api_out and sendmsg() transmits it. On the client side, recvmsg() captures the ancillary data. Out-of-order cached responses carry their fd so it is preserved until the matching recv call picks it up. Signed-off-by: Robin Jarry <rjarry@redhat.com> Signed-off-by: Vincent Jardin <vjardin@free.fr>
1 parent 0ab251b commit 4a3621f

4 files changed

Lines changed: 157 additions & 13 deletions

File tree

api/gr_api.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ gr_api_client_send(struct gr_api_client *, uint32_t req_type, size_t tx_len, con
5858
// Returns -EMSGSIZE if payload is non-empty but smaller than min_resp_size.
5959
int gr_api_client_recv(struct gr_api_client *, uint32_t req_type, uint32_t for_id, void **rx_data);
6060

61+
int gr_api_client_recv_fd(
62+
struct gr_api_client *,
63+
uint32_t req_type,
64+
uint32_t for_id,
65+
void **rx_data,
66+
int *fd
67+
);
68+
6169
// Send a request and receive the response.
6270
// Validates response payload size against GR_REQ-declared type.
6371
// Caller must free(*rx_data) after use.
@@ -78,6 +86,22 @@ static inline int gr_api_client_send_recv(
7886
// internal, called when interrupting gr_api_client_stream_foreach()
7987
int __gr_api_client_stream_drain(struct gr_api_client *, uint32_t req_type, uint32_t for_id);
8088

89+
// Send a request and receive the response with an optional file descriptor.
90+
// If fd is non-NULL and the server sends an fd via SCM_RIGHTS, it is stored in *fd.
91+
static inline int gr_api_client_send_recv_fd(
92+
struct gr_api_client *client,
93+
uint32_t req_type,
94+
size_t tx_len,
95+
const void *tx_data,
96+
void **rx_data,
97+
int *fd
98+
) {
99+
long int ret = gr_api_client_send(client, req_type, tx_len, tx_data);
100+
if (ret < 0)
101+
return ret;
102+
return gr_api_client_recv_fd(client, req_type, ret, rx_data, fd);
103+
}
104+
81105
// Send a request and iterate over the received stream of responses.
82106
//
83107
// @param obj Iterator variable (const pointer to response object type).

api/gr_api_client_impl.h

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ const char *gr_api_message_name(uint32_t type) {
101101
struct response {
102102
struct gr_api_response header;
103103
void *payload;
104+
int fd; // received via SCM_RIGHTS, -1 if none
104105
STAILQ_ENTRY(response) next;
105106
};
106107

@@ -153,6 +154,8 @@ int gr_api_client_disconnect(struct gr_api_client *client) {
153154
while (!STAILQ_EMPTY(&client->responses)) {
154155
struct response *resp = STAILQ_FIRST(&client->responses);
155156
STAILQ_REMOVE_HEAD(&client->responses, next);
157+
if (resp->fd >= 0)
158+
close(resp->fd);
156159
free(resp->payload);
157160
free(resp);
158161
}
@@ -224,16 +227,61 @@ long int gr_api_client_send(
224227
return req.id;
225228
}
226229

227-
int gr_api_client_recv(
230+
// Receive a response header, potentially with an SCM_RIGHTS fd.
231+
// Uses recvmsg() so ancillary data is captured.
232+
static int
233+
recv_response_header(const struct gr_api_client *c, struct gr_api_response *resp, int *recv_fd) {
234+
*recv_fd = -1;
235+
236+
union {
237+
char buf[CMSG_SPACE(sizeof(int))];
238+
struct cmsghdr align;
239+
} cmsg_buf;
240+
memset(&cmsg_buf, 0, sizeof(cmsg_buf));
241+
242+
struct iovec iov = {.iov_base = resp, .iov_len = sizeof(*resp)};
243+
struct msghdr msg = {
244+
.msg_iov = &iov,
245+
.msg_iovlen = 1,
246+
.msg_control = cmsg_buf.buf,
247+
.msg_controllen = sizeof(cmsg_buf.buf),
248+
};
249+
250+
ssize_t n = recvmsg(c->sock_fd, &msg, MSG_CMSG_CLOEXEC);
251+
252+
if (n == 0) {
253+
errno = ECONNRESET;
254+
return -1;
255+
}
256+
if (n < 0)
257+
return -1;
258+
if ((size_t)n < sizeof(*resp)) {
259+
errno = EPROTO;
260+
return -1;
261+
}
262+
263+
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
264+
if (cmsg != NULL && cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS)
265+
memcpy(recv_fd, CMSG_DATA(cmsg), sizeof(int));
266+
267+
return 0;
268+
}
269+
270+
int gr_api_client_recv_fd(
228271
struct gr_api_client *client,
229272
uint32_t req_type,
230273
uint32_t for_id,
231-
void **rx_data
274+
void **rx_data,
275+
int *fd
232276
) {
233277
struct response *cached = NULL;
234278
const struct api_message *m;
235279
struct gr_api_response resp;
236280
void *payload = NULL;
281+
int recv_fd = -1;
282+
283+
if (fd != NULL)
284+
*fd = -1;
237285

238286
if (client == NULL)
239287
return errno_set(EINVAL);
@@ -249,12 +297,13 @@ int gr_api_client_recv(
249297
STAILQ_REMOVE(&client->responses, cached, response, next);
250298
resp = cached->header;
251299
payload = cached->payload;
300+
recv_fd = cached->fd;
252301
free(cached);
253302
goto out;
254303
}
255304
recv:
256305
// No matching cached message, try to receive one from the socket.
257-
if (recv_all(client, &resp, sizeof(resp)) != sizeof(resp))
306+
if (recv_response_header(client, &resp, &recv_fd) < 0)
258307
goto err;
259308

260309
if (resp.payload_len > GR_API_MAX_MSG_LEN) {
@@ -275,8 +324,10 @@ int gr_api_client_recv(
275324
goto err;
276325
cached->header = resp;
277326
cached->payload = payload;
327+
cached->fd = recv_fd;
278328
STAILQ_INSERT_TAIL(&client->responses, cached, next);
279329
payload = NULL;
330+
recv_fd = -1;
280331
// And try to receive the next message until we get the correct ID.
281332
goto recv;
282333
}
@@ -299,13 +350,28 @@ int gr_api_client_recv(
299350
assert(rx_data != NULL);
300351
*rx_data = payload;
301352
}
353+
if (fd != NULL)
354+
*fd = recv_fd;
355+
else if (recv_fd >= 0)
356+
close(recv_fd);
302357

303358
return 0;
304359
err:
360+
if (recv_fd >= 0)
361+
close(recv_fd);
305362
free(payload);
306363
return -errno;
307364
}
308365

366+
int gr_api_client_recv(
367+
struct gr_api_client *client,
368+
uint32_t req_type,
369+
uint32_t for_id,
370+
void **rx_data
371+
) {
372+
return gr_api_client_recv_fd(client, req_type, for_id, rx_data, NULL);
373+
}
374+
309375
int gr_api_client_event_recv(const struct gr_api_client *c, struct gr_api_event **event) {
310376
const struct api_message *m;
311377
struct gr_api_event header;

main/api.c

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <stdlib.h>
2323
#include <sys/socket.h>
2424
#include <sys/stat.h>
25+
#include <sys/uio.h>
2526
#include <unistd.h>
2627

2728
LOG_TYPE("api");
@@ -249,6 +250,50 @@ void api_send(struct api_ctx *ctx, uint32_t len, const void *payload) {
249250
LOG(ERR, "pid=%d cannot write payload", ctx->pid);
250251
}
251252

253+
// Send a response header + optional payload together with a file
254+
// descriptor via sendmsg(SCM_RIGHTS). Closes the fd after sending.
255+
static void
256+
send_response_with_fd(struct bufferevent *bev, struct gr_api_response *resp, struct api_out *out) {
257+
bufferevent_flush(bev, EV_WRITE, BEV_FLUSH);
258+
259+
struct iovec iov[2];
260+
int iovlen = 1;
261+
iov[0].iov_base = resp;
262+
iov[0].iov_len = sizeof(*resp);
263+
if (out->len > 0 && out->payload != NULL) {
264+
iov[1].iov_base = out->payload;
265+
iov[1].iov_len = out->len;
266+
iovlen = 2;
267+
}
268+
269+
union {
270+
char buf[CMSG_SPACE(sizeof(int))];
271+
struct cmsghdr align;
272+
} cmsg_buf;
273+
memset(&cmsg_buf, 0, sizeof(cmsg_buf));
274+
275+
struct msghdr msg = {
276+
.msg_iov = iov,
277+
.msg_iovlen = iovlen,
278+
.msg_control = cmsg_buf.buf,
279+
.msg_controllen = sizeof(cmsg_buf.buf),
280+
};
281+
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
282+
cmsg->cmsg_level = SOL_SOCKET;
283+
cmsg->cmsg_type = SCM_RIGHTS;
284+
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
285+
memcpy(CMSG_DATA(cmsg), &out->fd, sizeof(int));
286+
287+
ssize_t ret;
288+
do {
289+
ret = sendmsg(bufferevent_getfd(bev), &msg, MSG_NOSIGNAL);
290+
} while (ret < 0 && errno == EINTR);
291+
if (ret < 0)
292+
LOG(ERR, "sendmsg with fd: %s", strerror(errno));
293+
close(out->fd);
294+
out->fd = -1;
295+
}
296+
252297
static void read_cb(struct bufferevent *bev, void *priv) {
253298
struct evbuffer *input = bufferevent_get_input(bev);
254299
struct api_ctx *ctx = priv;
@@ -294,7 +339,7 @@ static void read_cb(struct bufferevent *bev, void *priv) {
294339
// Reset state for next request
295340
ctx->header_complete = false;
296341

297-
struct api_out out;
342+
struct api_out out = {.fd = -1};
298343

299344
// We have a complete request, process it
300345
const struct api_handler *handler = lookup_api_handler(ctx->header.type);
@@ -333,16 +378,19 @@ static void read_cb(struct bufferevent *bev, void *priv) {
333378
.payload_len = out.len,
334379
};
335380

336-
if (bufferevent_write(bev, &resp, sizeof(resp)) < 0)
337-
LOG(ERR, "failed to write header");
338-
if (out.len > 0) {
339-
assert(out.payload != NULL);
340-
if (bufferevent_write(bev, out.payload, out.len) < 0)
341-
LOG(ERR, "failed to write payload");
381+
if (out.fd >= 0) {
382+
send_response_with_fd(bev, &resp, &out);
383+
} else {
384+
if (bufferevent_write(bev, &resp, sizeof(resp)) < 0)
385+
LOG(ERR, "failed to write header");
386+
if (out.len > 0) {
387+
assert(out.payload != NULL);
388+
if (bufferevent_write(bev, out.payload, out.len) < 0)
389+
LOG(ERR, "failed to write payload");
390+
}
391+
bufferevent_flush(bev, EV_WRITE, BEV_FLUSH);
342392
}
343393

344-
bufferevent_flush(bev, EV_WRITE, BEV_FLUSH);
345-
346394
free(req_payload);
347395
free(out.payload);
348396

main/module.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@ struct api_out {
1515
uint32_t status;
1616
uint32_t len;
1717
void *payload;
18+
int fd; // file descriptor to pass via SCM_RIGHTS, -1 = none
1819
};
1920

2021
static inline struct api_out api_out(uint32_t status, uint32_t len, void *payload) {
21-
struct api_out out = {status, len, payload};
22+
struct api_out out = {status, len, payload, -1};
23+
return out;
24+
}
25+
26+
static inline struct api_out api_out_fd(uint32_t status, uint32_t len, void *payload, int fd) {
27+
struct api_out out = {status, len, payload, fd};
2228
return out;
2329
}
2430

0 commit comments

Comments
 (0)