fixeria has uploaded this change for review.
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
To view, visit change 43601. To unsubscribe, or for help writing mail filters, visit settings.