fixeria submitted this change.

View Change



2 is the latest approved patch-set.
No files were changed between the latest approved patch-set and the submitted one.

Approvals: pespin: Looks good to me, but someone else must approve fixeria: Looks good to me, approved Jenkins Builder: Verified
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(-)

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

To view, visit change 43601. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: merged
Gerrit-Project: osmo-trx
Gerrit-Branch: master
Gerrit-Change-Id: I97075eb350e4270f4e909d493ba692e6b65be750
Gerrit-Change-Number: 43601
Gerrit-PatchSet: 4
Gerrit-Owner: fixeria <vyanitskiy@sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: fixeria <vyanitskiy@sysmocom.de>
Gerrit-Reviewer: laforge <laforge@osmocom.org>
Gerrit-Reviewer: pespin <pespin@sysmocom.de>