Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ IncludeCategories:
Priority: -1
- Regex: '^<gr_'
Priority: 100
- Regex: '^<(rte_|event|ecoli|numa.h|grout.h)'
- Regex: '^<(rte_|event|ecoli|pcap|numa.h|grout.h)'
Priority: 500
- Regex: '^<cmocka'
Priority: 100000
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
# grout dependencies
sudo apt-get update -qy
sudo apt-get install -qy --no-install-recommends \
make gcc gdb ccache ninja-build meson git scdoc \
make gcc gdb ccache ninja-build meson git scdoc bison flex \
libibverbs-dev libasan8 libcmocka-dev libedit-dev libarchive-dev \
libevent-dev libmnl-dev libnuma-dev python3-pyelftools \
socat tcpdump traceroute graphviz iproute2 iputils-ping ndisc6 jq \
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@
/subprojects/dpdk-*
/subprojects/libecoli-*
/subprojects/frr-*
/subprojects/libpcap
/subprojects/packagecache
/subprojects/.wraplock
29 changes: 28 additions & 1 deletion api/gr_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#include <stdlib.h>

// Must be bumped when making non-backward compatible changes in API headers
#define GR_API_VERSION 1
#define GR_API_VERSION 2

// API request header.
struct gr_api_request {
Expand Down Expand Up @@ -58,6 +58,14 @@ gr_api_client_send(struct gr_api_client *, uint32_t req_type, size_t tx_len, con
// Returns -EMSGSIZE if payload is non-empty but smaller than min_resp_size.
int gr_api_client_recv(struct gr_api_client *, uint32_t req_type, uint32_t for_id, void **rx_data);

int gr_api_client_recv_fd(
struct gr_api_client *,
uint32_t req_type,
uint32_t for_id,
void **rx_data,
int *fd
);

// Send a request and receive the response.
// Validates response payload size against GR_REQ-declared type.
// Caller must free(*rx_data) after use.
Expand All @@ -78,6 +86,22 @@ static inline int gr_api_client_send_recv(
// internal, called when interrupting gr_api_client_stream_foreach()
int __gr_api_client_stream_drain(struct gr_api_client *, uint32_t req_type, uint32_t for_id);

// Send a request and receive the response with an optional file descriptor.
// If fd is non-NULL and the server sends an fd via SCM_RIGHTS, it is stored in *fd.
static inline int gr_api_client_send_recv_fd(
struct gr_api_client *client,
uint32_t req_type,
size_t tx_len,
const void *tx_data,
void **rx_data,
int *fd
) {
long int ret = gr_api_client_send(client, req_type, tx_len, tx_data);
if (ret < 0)
return ret;
return gr_api_client_recv_fd(client, req_type, ret, rx_data, fd);
}

// Send a request and iterate over the received stream of responses.
//
// @param obj Iterator variable (const pointer to response object type).
Expand Down Expand Up @@ -156,6 +180,9 @@ const char *gr_api_message_name(uint32_t type);
code##_OBJ_SIZE = sizeof(obj) \
}
#endif
#ifndef GR_API_INLINE
#define GR_API_INLINE static inline
#endif

struct gr_empty { };

Expand Down
72 changes: 69 additions & 3 deletions api/gr_api_client_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ const char *gr_api_message_name(uint32_t type) {
struct response {
struct gr_api_response header;
void *payload;
int fd; // received via SCM_RIGHTS, -1 if none
STAILQ_ENTRY(response) next;
};

Expand Down Expand Up @@ -153,6 +154,8 @@ int gr_api_client_disconnect(struct gr_api_client *client) {
while (!STAILQ_EMPTY(&client->responses)) {
struct response *resp = STAILQ_FIRST(&client->responses);
STAILQ_REMOVE_HEAD(&client->responses, next);
if (resp->fd >= 0)
close(resp->fd);
free(resp->payload);
free(resp);
}
Expand Down Expand Up @@ -224,16 +227,61 @@ long int gr_api_client_send(
return req.id;
}

int gr_api_client_recv(
// Receive a response header, potentially with an SCM_RIGHTS fd.
// Uses recvmsg() so ancillary data is captured.
static int
recv_response_header(const struct gr_api_client *c, struct gr_api_response *resp, int *recv_fd) {
*recv_fd = -1;

union {
char buf[CMSG_SPACE(sizeof(int))];
struct cmsghdr align;
} cmsg_buf;
memset(&cmsg_buf, 0, sizeof(cmsg_buf));

struct iovec iov = {.iov_base = resp, .iov_len = sizeof(*resp)};
struct msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = cmsg_buf.buf,
.msg_controllen = sizeof(cmsg_buf.buf),
};

ssize_t n = recvmsg(c->sock_fd, &msg, MSG_CMSG_CLOEXEC);

if (n == 0) {
errno = ECONNRESET;
return -1;
}
if (n < 0)
return -1;
if ((size_t)n < sizeof(*resp)) {
errno = EPROTO;
return -1;
}

struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
if (cmsg != NULL && cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS)
memcpy(recv_fd, CMSG_DATA(cmsg), sizeof(int));

return 0;
}

int gr_api_client_recv_fd(
struct gr_api_client *client,
uint32_t req_type,
uint32_t for_id,
void **rx_data
void **rx_data,
int *fd
) {
struct response *cached = NULL;
const struct api_message *m;
struct gr_api_response resp;
void *payload = NULL;
int recv_fd = -1;

if (fd != NULL)
*fd = -1;

if (client == NULL)
return errno_set(EINVAL);
Expand All @@ -249,12 +297,13 @@ int gr_api_client_recv(
STAILQ_REMOVE(&client->responses, cached, response, next);
resp = cached->header;
payload = cached->payload;
recv_fd = cached->fd;
free(cached);
goto out;
}
recv:
// No matching cached message, try to receive one from the socket.
if (recv_all(client, &resp, sizeof(resp)) != sizeof(resp))
if (recv_response_header(client, &resp, &recv_fd) < 0)
goto err;

if (resp.payload_len > GR_API_MAX_MSG_LEN) {
Expand All @@ -275,8 +324,10 @@ int gr_api_client_recv(
goto err;
cached->header = resp;
cached->payload = payload;
cached->fd = recv_fd;
STAILQ_INSERT_TAIL(&client->responses, cached, next);
payload = NULL;
recv_fd = -1;
// And try to receive the next message until we get the correct ID.
goto recv;
}
Expand All @@ -299,13 +350,28 @@ int gr_api_client_recv(
assert(rx_data != NULL);
*rx_data = payload;
}
if (fd != NULL)
*fd = recv_fd;
else if (recv_fd >= 0)
close(recv_fd);

return 0;
err:
if (recv_fd >= 0)
close(recv_fd);
free(payload);
return -errno;
}

int gr_api_client_recv(
struct gr_api_client *client,
uint32_t req_type,
uint32_t for_id,
void **rx_data
) {
return gr_api_client_recv_fd(client, req_type, for_id, rx_data, NULL);
}

int gr_api_client_event_recv(const struct gr_api_client *c, struct gr_api_event **event) {
const struct api_message *m;
struct gr_api_event header;
Expand Down
36 changes: 34 additions & 2 deletions devtools/check_api.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ tar -C "$dir/check_api/a" -x --transform='s|.*/||'
# Exclude gr_api_client_impl.h which isn't a real API header.
rm -f $dir/check_api/*/gr_api_client_impl.h

cc_cmd="$cc_cmd -Wno-missing-declarations -Wno-missing-prototypes"
cc_cmd="$cc_cmd -fno-eliminate-unused-debug-types -Werror -O0 -g"

# Compile a dummy binary
Expand All @@ -63,6 +64,9 @@ for d in $dir/check_api/*; do
obj *e##_(void) { \\
return (void *)0; \\
}

#define GR_API_INLINE __attribute__((section(".api_inline")))

EOF
basename -a $d/*.h | sed 's/.*/#include <&>/'
} |
Expand All @@ -76,14 +80,42 @@ printf "Checking for API changes between %s and %s\n" \
$(git describe --long --abbrev=8 $prev_revision) \
$(git describe --long --abbrev=8 --dirty)

# Check for inline function body changes.
# GR_API_INLINE functions are placed in a dedicated ELF section so we can
# compare their disassembly to detect code changes.
{
objdump -t -j .api_inline $dir/check_api/a.bin 2>/dev/null || true
objdump -t -j .api_inline $dir/check_api/b.bin 2>/dev/null || true
} | awk '/F .api_inline/{print $NF}' | sort -u > $dir/check_api/inline_funcs

breaking=false
while read -r func; do
a=$(objdump -d --disassemble="$func" $dir/check_api/a.bin 2>/dev/null \
| sed -n '/^[0-9a-f].*:/{s/^[^:]*://;s/\t[0-9a-f ]*\t/\t/;p}')
b=$(objdump -d --disassemble="$func" $dir/check_api/b.bin 2>/dev/null \
| sed -n '/^[0-9a-f].*:/{s/^[^:]*://;s/\t[0-9a-f ]*\t/\t/;p}')
if [ -z "$a" ] && [ -z "$b" ]; then
continue
elif [ -z "$a" ]; then
echo "inline API function $func: added"
elif [ -z "$b" ]; then
breaking=true
echo "inline API function $func: removed"
elif [ "$a" != "$b" ]; then
breaking=true
echo "inline API function $func: code changed"
fi
done < $dir/check_api/inline_funcs

if ! $abidiff --non-reachable-types --drop-private-types --show-bytes \
--headers-dir1 $dir/check_api/a --headers-dir2 $dir/check_api/b \
$dir/check_api/a.bin $dir/check_api/b.bin >"$dir/abidiff.log" 2>&1
$dir/check_api/a.bin $dir/check_api/b.bin >"$dir/abidiff.log" 2>&1 \
|| [ "$breaking" = true ]
then
grep -vE '((Functions|Variables) changes|Unreachable types) summary:' "$dir/abidiff.log"
api_version_a=$(sed -nE 's/^#define GR_API_VERSION ([0-9]+).*/\1/p' $dir/check_api/a/*.h)
api_version_b=$(sed -nE 's/^#define GR_API_VERSION ([0-9]+).*/\1/p' $dir/check_api/b/*.h)
if grep -q '^ \[[DC]\]' "$dir/abidiff.log"; then
if grep -q '^ \[[DC]\]' "$dir/abidiff.log" || [ "$breaking" = true ]; then
echo "breaking API changes"
if [ "${api_version_a:-0}" -ge "${api_version_b:-0}" ]; then
grep -n '#define GR_API_VERSION' "$@"
Expand Down
2 changes: 1 addition & 1 deletion devtools/gen_api_header.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Copyright (c) 2026 Robin Jarry

echo "// SPDX-License-Identifier: BSD-3-Clause"
echo "// Copyright (c) $(date +Y) Red Hat"
echo "// Copyright (c) $(date +%Y) Red Hat"
echo
echo "#pragma once"
echo
Expand Down
2 changes: 1 addition & 1 deletion docs/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ custom_target(
# Individual command man pages
# The list is hardcoded since we can't run grcli during meson configuration.
grcli_commands = [
'address', 'affinity', 'conntrack', 'dnat44', 'events', 'fdb', 'flood',
'address', 'affinity', 'capture', 'conntrack', 'dnat44', 'events', 'fdb', 'flood',
'graph', 'interface', 'log', 'nexthop', 'ping', 'route', 'router-advert',
'snat44', 'stats', 'trace', 'traceroute', 'tunsrc',
]
Expand Down
66 changes: 57 additions & 9 deletions main/api.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <unistd.h>

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

// Send a response header + optional payload together with a file
// descriptor via sendmsg(SCM_RIGHTS). Closes the fd after sending.
static void
send_response_with_fd(struct bufferevent *bev, struct gr_api_response *resp, struct api_out *out) {
bufferevent_flush(bev, EV_WRITE, BEV_FLUSH);

struct iovec iov[2];
int iovlen = 1;
iov[0].iov_base = resp;
iov[0].iov_len = sizeof(*resp);
if (out->len > 0 && out->payload != NULL) {
iov[1].iov_base = out->payload;
iov[1].iov_len = out->len;
iovlen = 2;
}

union {
char buf[CMSG_SPACE(sizeof(int))];
struct cmsghdr align;
} cmsg_buf;
memset(&cmsg_buf, 0, sizeof(cmsg_buf));

struct msghdr msg = {
.msg_iov = iov,
.msg_iovlen = iovlen,
.msg_control = cmsg_buf.buf,
.msg_controllen = sizeof(cmsg_buf.buf),
};
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(cmsg), &out->fd, sizeof(int));

ssize_t ret;
do {
ret = sendmsg(bufferevent_getfd(bev), &msg, MSG_NOSIGNAL);
} while (ret < 0 && errno == EINTR);
if (ret < 0)
LOG(ERR, "sendmsg with fd: %s", strerror(errno));
close(out->fd);
out->fd = -1;
}

static void read_cb(struct bufferevent *bev, void *priv) {
struct evbuffer *input = bufferevent_get_input(bev);
struct api_ctx *ctx = priv;
Expand Down Expand Up @@ -294,7 +339,7 @@ static void read_cb(struct bufferevent *bev, void *priv) {
// Reset state for next request
ctx->header_complete = false;

struct api_out out;
struct api_out out = {.fd = -1};

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

if (bufferevent_write(bev, &resp, sizeof(resp)) < 0)
LOG(ERR, "failed to write header");
if (out.len > 0) {
assert(out.payload != NULL);
if (bufferevent_write(bev, out.payload, out.len) < 0)
LOG(ERR, "failed to write payload");
if (out.fd >= 0) {
send_response_with_fd(bev, &resp, &out);
} else {
if (bufferevent_write(bev, &resp, sizeof(resp)) < 0)
LOG(ERR, "failed to write header");
if (out.len > 0) {
assert(out.payload != NULL);
if (bufferevent_write(bev, out.payload, out.len) < 0)
LOG(ERR, "failed to write payload");
}
bufferevent_flush(bev, EV_WRITE, BEV_FLUSH);
}

bufferevent_flush(bev, EV_WRITE, BEV_FLUSH);

free(req_payload);
free(out.payload);

Expand Down
Loading
Loading