fixeria has submitted this change. ( https://gerrit.osmocom.org/c/osmo-trx/+/43601?usp=email )
(
2 is the latest approved patch-set. No files were changed between the latest approved patch-set and the submitted one. )Change subject: libosmo-trx/ep: allow ctrl socket to accept/reply to any peer ......................................................................
libosmo-trx/ep: allow ctrl socket to accept/reply to any peer
By default, the socket stays connect()ed to the configured peer, so the kernel silently drops datagrams from anyone else. This is fine for the normal osmo-bts/osmo-trx/trxcon use case, where both ends of the link are fixed and known ahead of time.
Add an optional promiscuous mode that enables ttcn3-bts-test to inject path simulation TRXC commands from its own source port: when enabled, leave the ctrl socket unconnected and always reply to the actual sender of the last received CMD (tracked in chan->ctrl_peer), rather than only the configured peer.
This mode will be used by the upcoming osmo-trx-proxy.
Change-Id: I97075eb350e4270f4e909d493ba692e6b65be750 Related: OS#6672 --- M libosmo-trx/include/osmocom/trx/ep.h M libosmo-trx/src/trx_ep.c M tests/libosmo-trx/trx_ep_test.c M tests/libosmo-trx/trx_ep_test.err M tests/libosmo-trx/trx_ep_test.ok 5 files changed, 171 insertions(+), 25 deletions(-)
Approvals: pespin: Looks good to me, but someone else must approve fixeria: Looks good to me, approved Jenkins Builder: Verified
diff --git a/libosmo-trx/include/osmocom/trx/ep.h b/libosmo-trx/include/osmocom/trx/ep.h index af2c7be..0ffacde 100644 --- a/libosmo-trx/include/osmocom/trx/ep.h +++ b/libosmo-trx/include/osmocom/trx/ep.h @@ -106,6 +106,10 @@ int osmo_trx_ep_set_clock_socket(struct osmo_trx_ep *ep, bool enable); bool osmo_trx_ep_get_clock_socket(const struct osmo_trx_ep *ep);
+/*! Unconnected ctrl socket accepting/replying to any peer; default: false */ +int osmo_trx_ep_set_ctrl_promisc(struct osmo_trx_ep *ep, bool enable); +bool osmo_trx_ep_get_ctrl_promisc(const struct osmo_trx_ep *ep); + /*! Per-channel TRXD PDU version in use (set after SETFORMAT negotiation) */ int osmo_trx_ep_set_pdu_ver(struct osmo_trx_ep *ep, unsigned int chan, uint8_t ver); int osmo_trx_ep_get_pdu_ver(const struct osmo_trx_ep *ep, unsigned int chan); diff --git a/libosmo-trx/src/trx_ep.c b/libosmo-trx/src/trx_ep.c index 35cabbd..1a478a8 100644 --- a/libosmo-trx/src/trx_ep.c +++ b/libosmo-trx/src/trx_ep.c @@ -33,6 +33,7 @@ #include <stdarg.h> #include <unistd.h>
+#include <sys/socket.h> #include <netinet/in.h>
#include <osmocom/core/talloc.h> @@ -59,12 +60,16 @@ struct osmo_io_fd *data_iofd; uint8_t pdu_ver; /* TRXD PDU version in use */ struct msgb *tx_msg; /* pending TRXDv2 Tx batch */ - /* Bytes handed to osmo_iofd_write_msgb() on ctrl_iofd but not yet - * completed (per trx_ep_ctrl_write_cb()), used by trx_ep_ctrl_close() + /* Bytes handed to osmo_iofd_sendto_msgb() on ctrl_iofd but not yet + * completed (per trx_ep_ctrl_sendto_cb()), used by trx_ep_ctrl_close() * to tell whether a flush is needed on teardown. Non-zero here also * means ctrl_iofd is currently being flushed asynchronously after * osmo_trx_ep_close(); see osmo_trx_ep_is_closing(). */ size_t ctrl_wr_pending; + /* Destination for the next ctrl message sent on ctrl_iofd. + * Initialized to the configured peer at open, updated in + * trx_ep_ctrl_recvfrom_cb(). */ + struct osmo_sockaddr ctrl_peer; };
/*! Sockets currently bound */ @@ -72,9 +77,11 @@ /*! Enable the clock socket (base_port + 0) */ #define OSMO_TRX_EP_F_CLOCK_SOCKET (1 << 1) /*! osmo_trx_ep_free() was called while a chan's ctrl socket was still - * flushing: the actual free is deferred to trx_ep_ctrl_close_write_cb(), + * flushing: the actual free is deferred to trx_ep_ctrl_close_sendto_cb(), * once the last one completes. */ #define OSMO_TRX_EP_F_PENDING_FREE (1 << 2) +/*! Leave the ctrl socket unconnected, accepting/replying to any peer */ +#define OSMO_TRX_EP_F_CTRL_PROMISC (1 << 3)
struct osmo_trx_ep { uint32_t flags; /* see OSMO_TRX_EP_F_* */ @@ -173,7 +180,8 @@ msgb_free(msg); }
-static void trx_ep_ctrl_read_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg) +static void trx_ep_ctrl_recvfrom_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg, + const struct osmo_sockaddr *saddr) { struct osmo_trx_ep_chan *chan = osmo_iofd_get_data(iofd); struct osmo_trx_ep *ep = chan->ep; @@ -183,6 +191,10 @@ if (res <= 0) goto ret_free_msg;
+ /* Remember the sender of every datagram received on ctrl_iofd, + * so the next reply goes back to whoever actually sent it. */ + chan->ctrl_peer = *saddr; + rc = osmo_trxc_msg_parse(&tmsg, (const char *)msgb_data(msg), msgb_length(msg)); if (rc < 0) { LOGEPCH(ep, chan->num, LOGL_NOTICE, "Rx malformed TRXC message (rc=%d)\n", rc); @@ -250,7 +262,8 @@
/* Track bytes still pending on a channel's ctrl_iofd, so trx_ep_ctrl_close() * can tell whether anything is still in flight at teardown time. */ -static void trx_ep_ctrl_write_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg) +static void trx_ep_ctrl_sendto_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg, + const struct osmo_sockaddr *daddr) { struct osmo_trx_ep_chan *chan = osmo_iofd_get_data(iofd);
@@ -282,7 +295,8 @@ * the iofd and, if osmo_trx_ep_free() was called on the (kept alive) * endpoint in the meantime and no other channel is still flushing, * finally frees the endpoint too. */ -static void trx_ep_ctrl_close_write_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg) +static void trx_ep_ctrl_close_sendto_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg, + const struct osmo_sockaddr *daddr) { struct osmo_trx_ep_chan *chan = osmo_iofd_get_data(iofd); struct osmo_trx_ep *ep = chan->ep; @@ -324,7 +338,7 @@ }
static const struct osmo_io_ops trx_ep_ctrl_close_ioops = { - .write_cb = &trx_ep_ctrl_close_write_cb, + .sendto_cb = &trx_ep_ctrl_close_sendto_cb, };
/* Close a channel's ctrl_iofd, flushing (best-effort) any still-in-flight @@ -357,8 +371,8 @@ };
static const struct osmo_io_ops trx_ep_ctrl_ioops = { - .read_cb = &trx_ep_ctrl_read_cb, - .write_cb = &trx_ep_ctrl_write_cb, + .recvfrom_cb = &trx_ep_ctrl_recvfrom_cb, + .sendto_cb = &trx_ep_ctrl_sendto_cb, };
static const struct osmo_io_ops trx_ep_data_ioops = { @@ -366,26 +380,34 @@ .write_cb = &trx_ep_write_cb, };
-/* Open a single UDP socket (base port + ofs) and set up osmo_io for it */ +/* Open a single UDP socket (base port + ofs) and set up osmo_io for it. + * connect_socket == false leaves the socket unconnected (bind-only), + * accepting datagrams from and replying to any peer. */ static struct osmo_io_fd *trx_ep_open_iofd(struct osmo_trx_ep *ep, uint16_t ofs, const struct osmo_io_ops *ioops, - unsigned int buf_size, void *data) + enum osmo_io_fd_mode mode, + unsigned int buf_size, void *data, + bool connect_socket) { + unsigned int flags = OSMO_SOCK_F_BIND | OSMO_SOCK_F_NONBLOCK; char sock_name[OSMO_SOCK_NAME_MAXLEN]; struct osmo_io_fd *iofd; int fd;
+ if (connect_socket) + flags |= OSMO_SOCK_F_CONNECT; + fd = osmo_sock_init2(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, ep->laddr, trx_ep_port(ep, true, ofs), ep->raddr, trx_ep_port(ep, false, ofs), - OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT | OSMO_SOCK_F_NONBLOCK); + flags); if (fd < 0) { LOGEP(ep, LOGL_ERROR, "Failed to open a socket (ofs=%u): %d\n", ofs, fd); return NULL; }
osmo_sock_get_name_buf(sock_name, sizeof(sock_name), fd); - iofd = osmo_iofd_setup(ep, fd, sock_name, OSMO_IO_FD_MODE_READ_WRITE, ioops, data); + iofd = osmo_iofd_setup(ep, fd, sock_name, mode, ioops, data); if (iofd == NULL) { close(fd); return NULL; @@ -415,16 +437,36 @@ static int trx_ep_chan_open(struct osmo_trx_ep_chan *chan) { struct osmo_trx_ep *ep = chan->ep; + const bool connect_ctrl = !(ep->flags & OSMO_TRX_EP_F_CTRL_PROMISC);
chan->ctrl_iofd = trx_ep_open_iofd(ep, 2 * chan->num + 1, &trx_ep_ctrl_ioops, - OSMO_TRXC_MSG_BUF_SIZE, chan); + OSMO_IO_FD_MODE_RECVFROM_SENDTO, + OSMO_TRXC_MSG_BUF_SIZE, chan, connect_ctrl); if (chan->ctrl_iofd == NULL) { LOGEPCH(ep, chan->num, LOGL_ERROR, "Failed to open TRXC socket\n"); return -EIO; }
+ /* connect()ed: seed ctrl_peer with the actual peer so a CMD sent + * from the L1 side (osmo_trx_ep mode L1) before ever receiving + * anything has somewhere to go; trx_ep_ctrl_recvfrom_cb() keeps it + * up to date afterwards. */ + if (connect_ctrl) { + struct osmo_sockaddr *peer = &chan->ctrl_peer; + socklen_t peer_len = sizeof(peer->u); + + if (getpeername(osmo_iofd_get_fd(chan->ctrl_iofd), &peer->u.sa, &peer_len) < 0) { + /* not fatal: ctrl_peer just stays unset until the first inbound + * datagram updates it via trx_ep_ctrl_recvfrom_cb() */ + LOGEPCH(ep, chan->num, LOGL_ERROR, + "getpeername() failed on TRXC socket: %s\n", + strerror(errno)); + } + } + chan->data_iofd = trx_ep_open_iofd(ep, 2 * chan->num + 2, &trx_ep_data_ioops, - TRX_EP_DATA_BUF_SIZE, chan); + OSMO_IO_FD_MODE_READ_WRITE, + TRX_EP_DATA_BUF_SIZE, chan, true); if (chan->data_iofd == NULL) { LOGEPCH(ep, chan->num, LOGL_ERROR, "Failed to open TRXD socket\n"); return -EIO; @@ -438,7 +480,7 @@ { /* trx_ep_ctrl_close() clears chan->ctrl_iofd itself, but only once * it's actually safe to: immediately if nothing was pending, or - * later from trx_ep_ctrl_close_write_cb() if a flush is needed. */ + * later from trx_ep_ctrl_close_sendto_cb() if a flush is needed. */ trx_ep_ctrl_close(chan); osmo_iofd_free(chan->data_iofd); chan->data_iofd = NULL; @@ -504,7 +546,8 @@
if (ep->flags & OSMO_TRX_EP_F_CLOCK_SOCKET) { ep->clck_iofd = trx_ep_open_iofd(ep, 0, &trx_ep_clck_ioops, - OSMO_TRXC_MSG_BUF_SIZE, ep); + OSMO_IO_FD_MODE_READ_WRITE, + OSMO_TRXC_MSG_BUF_SIZE, ep, true); if (ep->clck_iofd == NULL) goto ret_error; } @@ -551,7 +594,7 @@
/*! Free the given endpoint instance (closes all sockets). If a ctrl socket * is still flushing (see osmo_trx_ep_is_closing()), the endpoint itself is - * kept alive until the flush completes (see trx_ep_ctrl_close_write_cb()), + * kept alive until the flush completes (see trx_ep_ctrl_close_sendto_cb()), * which then finishes this deferred free. */ void osmo_trx_ep_free(struct osmo_trx_ep *ep) { @@ -734,6 +777,29 @@ return ep->flags & OSMO_TRX_EP_F_CLOCK_SOCKET; }
+/*! Enable/disable promiscuous ctrl socket mode (default: false): leaves the + * ctrl socket unconnected, accepting TRXC PDUs from any peer instead of only + * the configured one, e.g. a test tool injecting extra TRXC commands from + * its own socket. The RSP always goes back to whoever actually sent the + * CMD, promiscuous or not (see trx_ep_ctrl_recvfrom_cb() and chan->ctrl_peer). */ +int osmo_trx_ep_set_ctrl_promisc(struct osmo_trx_ep *ep, bool enable) +{ + if (ep->flags & OSMO_TRX_EP_F_OPEN) + return -EBUSY; + + if (enable) + ep->flags |= OSMO_TRX_EP_F_CTRL_PROMISC; + else + ep->flags &= ~OSMO_TRX_EP_F_CTRL_PROMISC; + return 0; +} + +/*! Whether promiscuous ctrl socket mode is enabled */ +bool osmo_trx_ep_get_ctrl_promisc(const struct osmo_trx_ep *ep) +{ + return ep->flags & OSMO_TRX_EP_F_CTRL_PROMISC; +} + /*! Set the name (log prefix) of the given instance, e.g. "phy0" */ int osmo_trx_ep_set_name(struct osmo_trx_ep *ep, const char *fmt, ...) { @@ -820,11 +886,13 @@ int osmo_trx_ep_send_ctrl_msg(struct osmo_trx_ep *ep, unsigned int chan, const struct osmo_trxc_msg *tmsg) { + struct osmo_trx_ep_chan *c; struct msgb *msg; size_t len; int rc;
OSMO_ASSERT(chan < ep->num_chans); + c = &ep->chans[chan];
msg = msgb_alloc_c(ep, OSMO_TRXC_MSG_BUF_SIZE, "trx_ep_ctrl_tx"); rc = osmo_trxc_msg_build((char *)msgb_data(msg), msgb_tailroom(msg), tmsg); @@ -835,11 +903,11 @@ msgb_put(msg, rc); len = msgb_length(msg);
- rc = osmo_iofd_write_msgb(ep->chans[chan].ctrl_iofd, msg); + rc = osmo_iofd_sendto_msgb(c->ctrl_iofd, msg, 0, &c->ctrl_peer); if (rc < 0) msgb_free(msg); else - ep->chans[chan].ctrl_wr_pending += len; + c->ctrl_wr_pending += len; return rc; }
diff --git a/tests/libosmo-trx/trx_ep_test.c b/tests/libosmo-trx/trx_ep_test.c index 7f96540..7b7a123 100644 --- a/tests/libosmo-trx/trx_ep_test.c +++ b/tests/libosmo-trx/trx_ep_test.c @@ -27,6 +27,11 @@ #include <errno.h> #include <stdio.h> #include <string.h> +#include <unistd.h> + +#include <arpa/inet.h> +#include <netinet/in.h> +#include <sys/socket.h>
#include <osmocom/core/application.h> #include <osmocom/core/logging.h> @@ -37,6 +42,7 @@ #include <osmocom/trx/ep.h>
#define TEST_BASE_PORT 16700 +#define TEST_PROMISC_BASE_PORT 16720
static void *test_ctx = NULL;
@@ -348,6 +354,66 @@ ep_close_free(ep_trx); }
+/* osmo_trx_ep_{get,set}_ctrl_promisc(): with promisc enabled, a channel's + * ctrl socket is left unconnected (bind-only), so it accepts a CMD from any + * peer, not just the configured raddr, and replies to whoever actually sent + * it - e.g. a test tool injecting extra TRXC commands from its own socket. */ +static void test_ctrl_promisc(void) +{ + struct sockaddr_in dst = { .sin_family = AF_INET }; + struct sockaddr_in from; + socklen_t from_len; + struct osmo_trx_ep *ep; + int fd, rc, len; + + printf("=== %s(): starting testcase ===\n", __func__); + + ep = ep_alloc("promisc", OSMO_TRX_EP_MODE_TRX); + osmo_trx_ep_set_base_port(ep, TEST_PROMISC_BASE_PORT); + + OSMO_ASSERT(osmo_trx_ep_get_ctrl_promisc(ep) == false); + OSMO_ASSERT(osmo_trx_ep_set_ctrl_promisc(ep, true) == 0); + OSMO_ASSERT(osmo_trx_ep_get_ctrl_promisc(ep) == true); + + ep_open(ep); + + /* config setters, including this one, are expected to fail once open */ + OSMO_ASSERT(osmo_trx_ep_set_ctrl_promisc(ep, false) == -EBUSY); + + /* chan 0 ctrl; source port of the foreign peer is left to the kernel + * (no bind() before sendto() below) */ + dst.sin_port = htons(TEST_PROMISC_BASE_PORT + 1); + OSMO_ASSERT(inet_pton(AF_INET, "127.0.0.1", &dst.sin_addr) == 1); + + fd = socket(AF_INET, SOCK_DGRAM, 0); + OSMO_ASSERT(fd >= 0); + + char buf[OSMO_TRXC_MSG_BUF_SIZE]; + static const struct osmo_trxc_msg cmd_poweron = { + .type = OSMO_TRXC_MT_CMD, + .cmd = OSMO_TRXC_CMD_POWERON, + }; + + len = osmo_trxc_msg_build(buf, sizeof(buf), &cmd_poweron); + OSMO_ASSERT(len > 0); + rc = sendto(fd, buf, len, 0, (const struct sockaddr *)&dst, sizeof(dst)); + OSMO_ASSERT(rc == len); + + flush_io(); + + /* the RSP must come back to us (the foreign sender), not to raddr */ + from_len = sizeof(from); + rc = recvfrom(fd, buf, sizeof(buf) - 1, MSG_DONTWAIT, + (struct sockaddr *)&from, &from_len); + OSMO_ASSERT(rc > 0); + buf[rc] = '\0'; + OSMO_ASSERT(from.sin_addr.s_addr == dst.sin_addr.s_addr); + printf("foreign rx: '%s'\n", buf); + + close(fd); + ep_close_free(ep); +} + int main(int argc, char **argv) { test_ctx = talloc_named_const(NULL, 0, "trx_ep_test"); @@ -365,6 +431,8 @@ test_ctrl_close_flush(false); test_ctrl_close_flush(true);
+ test_ctrl_promisc(); + printf("Done\n"); return 0; } diff --git a/tests/libosmo-trx/trx_ep_test.err b/tests/libosmo-trx/trx_ep_test.err index 4f7e5e9..f786f6b 100644 --- a/tests/libosmo-trx/trx_ep_test.err +++ b/tests/libosmo-trx/trx_ep_test.err @@ -22,8 +22,8 @@ DLGLOBAL DEBUG (ep=ep_bts, chan=0) trx_ep_ctrl_close(): 12 byte(s) still pending, flushing asynchronously DLGLOBAL DEBUG (ep=ep_bts, chan=1) trx_ep_ctrl_close(): 12 byte(s) still pending, flushing asynchronously DLGLOBAL DEBUG (ep=ep_bts, chan=2) trx_ep_ctrl_close(): nothing pending, closing immediately -DLGLOBAL DEBUG (ep=ep_bts, chan=0) trx_ep_ctrl_close_write_cb(): wrote 12 byte(s), flush completed -DLGLOBAL DEBUG (ep=ep_bts, chan=1) trx_ep_ctrl_close_write_cb(): wrote 12 byte(s), flush completed +DLGLOBAL DEBUG (ep=ep_bts, chan=0) trx_ep_ctrl_close_sendto_cb(): wrote 12 byte(s), flush completed +DLGLOBAL DEBUG (ep=ep_bts, chan=1) trx_ep_ctrl_close_sendto_cb(): wrote 12 byte(s), flush completed DLGLOBAL INFO (ep=ep_trx) Closing TRXC/TRXD connections l=127.0.0.1:16700<->r=127.0.0.1:16800 DLGLOBAL DEBUG (ep=ep_trx, chan=0) trx_ep_ctrl_close(): nothing pending, closing immediately DLGLOBAL DEBUG (ep=ep_trx, chan=1) trx_ep_ctrl_close(): nothing pending, closing immediately @@ -34,10 +34,13 @@ DLGLOBAL DEBUG (ep=ep_bts, chan=0) trx_ep_ctrl_close(): 12 byte(s) still pending, flushing asynchronously DLGLOBAL DEBUG (ep=ep_bts, chan=1) trx_ep_ctrl_close(): 12 byte(s) still pending, flushing asynchronously DLGLOBAL DEBUG (ep=ep_bts, chan=2) trx_ep_ctrl_close(): nothing pending, closing immediately -DLGLOBAL DEBUG (ep=ep_bts, chan=0) trx_ep_ctrl_close_write_cb(): wrote 12 byte(s), flush completed -DLGLOBAL DEBUG (ep=ep_bts, chan=1) trx_ep_ctrl_close_write_cb(): wrote 12 byte(s), flush completed -DLGLOBAL DEBUG (ep=ep_bts) trx_ep_ctrl_close_write_cb(): last flush completed, free()ing +DLGLOBAL DEBUG (ep=ep_bts, chan=0) trx_ep_ctrl_close_sendto_cb(): wrote 12 byte(s), flush completed +DLGLOBAL DEBUG (ep=ep_bts, chan=1) trx_ep_ctrl_close_sendto_cb(): wrote 12 byte(s), flush completed +DLGLOBAL DEBUG (ep=ep_bts) trx_ep_ctrl_close_sendto_cb(): last flush completed, free()ing DLGLOBAL INFO (ep=ep_trx) Closing TRXC/TRXD connections l=127.0.0.1:16700<->r=127.0.0.1:16800 DLGLOBAL DEBUG (ep=ep_trx, chan=0) trx_ep_ctrl_close(): nothing pending, closing immediately DLGLOBAL DEBUG (ep=ep_trx, chan=1) trx_ep_ctrl_close(): nothing pending, closing immediately DLGLOBAL DEBUG (ep=ep_trx, chan=2) trx_ep_ctrl_close(): nothing pending, closing immediately +DLGLOBAL INFO (ep=ep_promisc) Opening TRXC/TRXD connections l=127.0.0.1:16720<->r=127.0.0.1:16820 +DLGLOBAL INFO (ep=ep_promisc) Closing TRXC/TRXD connections l=127.0.0.1:16720<->r=127.0.0.1:16820 +DLGLOBAL DEBUG (ep=ep_promisc, chan=0) trx_ep_ctrl_close(): nothing pending, closing immediately diff --git a/tests/libosmo-trx/trx_ep_test.ok b/tests/libosmo-trx/trx_ep_test.ok index 194e5d5..ad597dd 100644 --- a/tests/libosmo-trx/trx_ep_test.ok +++ b/tests/libosmo-trx/trx_ep_test.ok @@ -36,4 +36,7 @@ bts: closed_cb() trx: rx_ctrl_msg(chan=0): 'CMD POWEROFF' trx: rx_ctrl_msg(chan=1): 'CMD RFMUTE 1' +=== test_ctrl_promisc(): starting testcase === +promisc: rx_ctrl_msg(chan=0): 'CMD POWERON' +foreign rx: 'RSP POWERON 0' Done