fixeria has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-trx/+/43601?usp=email )
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, 163 insertions(+), 17 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-trx refs/changes/01/43601/1
diff --git a/libosmo-trx/include/osmocom/trx/ep.h b/libosmo-trx/include/osmocom/trx/ep.h index ad02719..f7ff75c 100644 --- a/libosmo-trx/include/osmocom/trx/ep.h +++ b/libosmo-trx/include/osmocom/trx/ep.h @@ -100,6 +100,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 8d9d641..37ac72b 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> @@ -65,12 +66,18 @@ * to tell whether a flush is needed on teardown (see struct * trx_ep_ctrl_flush below). */ 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 */ #define OSMO_TRX_EP_F_OPEN (1 << 0) /*! Enable the clock socket (base_port + 0) */ #define OSMO_TRX_EP_F_CLOCK_SOCKET (1 << 1) +/*! Leave the ctrl socket unconnected, accepting/replying to any peer */ +#define OSMO_TRX_EP_F_CTRL_PROMISC (1 << 2)
struct osmo_trx_ep { uint32_t flags; /* see OSMO_TRX_EP_F_* */ @@ -168,7 +175,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; @@ -178,6 +186,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); @@ -247,7 +259,8 @@ * can tell whether anything is still in flight at teardown time (see * struct trx_ep_ctrl_flush below for why osmo_iofd_txqueue_len() alone * cannot answer that question). */ -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,7 @@ size_t wr_pending; };
-/*! Write call-back for a ctrl_iofd being flushed asynchronously after +/*! Sendto call-back for a ctrl_iofd being flushed asynchronously after * osmo_trx_ep_close(): a 'goodbye' TRXC message (e.g. "CMD POWEROFF") may * have been enqueued via osmo_trx_ep_send_ctrl_msg() right before tearing * down the endpoint; osmo_iofd_free() would otherwise drop it together @@ -291,7 +304,8 @@ * returns) and lives on its own, driven by the same event loop, until * every byte enqueued on it has actually completed (or a write fails), * at which point it closes and frees itself. */ -static void trx_ep_ctrl_flush_write_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg) +static void trx_ep_ctrl_flush_sendto_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg, + const struct osmo_sockaddr *daddr) { struct trx_ep_ctrl_flush *flush = osmo_iofd_get_data(iofd);
@@ -316,7 +330,7 @@ }
static const struct osmo_io_ops trx_ep_ctrl_flush_ioops = { - .write_cb = &trx_ep_ctrl_flush_write_cb, + .sendto_cb = &trx_ep_ctrl_flush_sendto_cb, };
/* Close a channel's ctrl_iofd, flushing (best-effort) any still-in-flight @@ -361,8 +375,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 = { @@ -370,26 +384,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; @@ -419,16 +441,30 @@ 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); + + getpeername(osmo_iofd_get_fd(chan->ctrl_iofd), &peer->u.sa, &peer_len); + } + 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; @@ -507,7 +543,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; } @@ -709,6 +746,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, ...) { @@ -795,11 +855,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); @@ -810,11 +872,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 54fbec9..8d6d880 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;
@@ -125,6 +131,72 @@ bi->burst[i] = (i & 1) ? -100 : 100; }
+/* 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("=== ctrl promisc: foreign CMD accepted by an unconnected ctrl socket ===\n"); + + ep = osmo_trx_ep_alloc(test_ctx, 1); + OSMO_ASSERT(ep != NULL); + osmo_trx_ep_set_priv(ep, "promisc"); + osmo_trx_ep_set_mode(ep, OSMO_TRX_EP_MODE_TRX); + osmo_trx_ep_set_laddr(ep, "127.0.0.1"); + osmo_trx_ep_set_raddr(ep, "127.0.0.1"); + osmo_trx_ep_set_base_port(ep, TEST_PROMISC_BASE_PORT); + osmo_trx_ep_set_name(ep, "ep_%s", "promisc"); + + 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); + + OSMO_ASSERT(osmo_trx_ep_open(ep) == 0); + + /* 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]; + const struct osmo_trxc_msg cmd = { + .type = OSMO_TRXC_MT_CMD, + .cmd = "POWERON", + }; + + len = osmo_trxc_msg_build(buf, sizeof(buf), &cmd); + 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); + osmo_trx_ep_free(ep); +} + int main(int argc, char **argv) { struct osmo_trx_ep *ep_l1, *ep_trx; @@ -248,6 +320,8 @@ osmo_trx_ep_free(ep_l1); osmo_trx_ep_free(ep_trx);
+ 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 87cce6c..46bab0a 100644 --- a/tests/libosmo-trx/trx_ep_test.err +++ b/tests/libosmo-trx/trx_ep_test.err @@ -5,7 +5,10 @@ DLGLOBAL INFO (ep=ep_l1) Closing TRXC/TRXD connections l=127.0.0.1:16800<->r=127.0.0.1:16700 DLGLOBAL DEBUG (ep=ep_l1, chan=0) trx_ep_ctrl_close(): nothing pending, closing immediately DLGLOBAL DEBUG (ep=ep_l1, chan=1) trx_ep_ctrl_close(): 12 byte(s) still pending, detaching to flush asynchronously -DLGLOBAL DEBUG (ep=ep_l1, chan=1) trx_ep_ctrl_flush_write_cb(): flush completed (res=12) +DLGLOBAL DEBUG (ep=ep_l1, chan=1) trx_ep_ctrl_flush_sendto_cb(): flush completed (res=12) 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 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 bd43fd5..d554a15 100644 --- a/tests/libosmo-trx/trx_ep_test.ok +++ b/tests/libosmo-trx/trx_ep_test.ok @@ -17,4 +17,7 @@ l1: rx_burst_ind(chan=0): NOPE.ind tn=5 fn=200005 trx_num=0 rssi=-63 toa256=-512 C/I=-150 cB === TRXC CMD sent right before osmo_trx_ep_close() (L1 -> TRX) === trx: rx_ctrl_msg(chan=1): 'CMD POWEROFF' +=== ctrl promisc: foreign CMD accepted by an unconnected ctrl socket === +promisc: rx_ctrl_msg(chan=0): 'CMD POWERON' +foreign rx: 'RSP POWERON 0' Done