laforge submitted this change.

View Change

Approvals: Jenkins Builder: Verified laforge: Looks good to me, approved
OCTOI: initial support for E1oIP forwarding

This introduces initial support for operation as OCTOI (Osmocom
Community TDMoIP) server and client operation.

Various features are still absent (user authentication, support for
re-ordered packets), but this version is already able to provide
services to clients with dynamic IP addresses as well as servers.

The bulk of the OCTOI / E1oIP code is implemented as a shared library,
to facilitate the development of other servers and clients in the
future, and also to minimize the impact on the existing osmo-e1d code
base.

More information is available at https://osmocom.org/projects/octoi/wiki

Change-Id: I05f5ff697ca8f7dccdcf89660f12089babfcc92e
---
M Makefile.am
M configure.ac
M debian/control
A debian/libosmo-octoi-dev.install
A debian/libosmo-octoi0.install
M include/Makefile.am
M include/osmocom/e1d/proto.h
A include/osmocom/octoi/e1oip_proto.h
A include/osmocom/octoi/octoi.h
A libosmo-octoi.pc.in
M src/Makefile.am
M src/ctl.c
M src/e1d.h
A src/e1oip.c
M src/intf_line.c
M src/mux_demux.c
A src/octoi/Makefile.am
A src/octoi/e1oip.c
A src/octoi/e1oip.h
A src/octoi/frame_fifo.c
A src/octoi/frame_fifo.h
A src/octoi/libosmo-octoi.map
A src/octoi/octoi.c
A src/octoi/octoi.h
A src/octoi/octoi_clnt_fsm.c
A src/octoi/octoi_clnt_vty.c
A src/octoi/octoi_fsm.c
A src/octoi/octoi_fsm.h
A src/octoi/octoi_sock.c
A src/octoi/octoi_sock.h
A src/octoi/octoi_srv_fsm.c
A src/octoi/octoi_srv_vty.c
A src/octoi/octoi_vty.h
M src/osmo-e1d.c
M src/usb.c
M src/vty.c
36 files changed, 3,427 insertions(+), 12 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 3f87824..5027905 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -15,7 +15,7 @@
$(NULL)

pkgconfigdir = $(libdir)/pkgconfig
-pkgconfig_DATA = libosmo-e1d.pc
+pkgconfig_DATA = libosmo-e1d.pc libosmo-octoi.pc

AM_DISTCHECK_CONFIGURE_FLAGS = \
--with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir)
diff --git a/configure.ac b/configure.ac
index f5c15f2..5a09386 100644
--- a/configure.ac
+++ b/configure.ac
@@ -97,6 +97,8 @@
doc/Makefile
doc/examples/Makefile
src/Makefile
+ src/octoi/Makefile
include/Makefile
libosmo-e1d.pc
+ libosmo-octoi.pc
)
diff --git a/debian/control b/debian/control
index f1e35b7..4f0109e 100644
--- a/debian/control
+++ b/debian/control
@@ -40,3 +40,20 @@
libosmo-e1d1 (= ${binary:Version}),
libosmocore-dev,
Description: Development headers for the osmo-e1d library.
+
+Package: libosmo-octoi0
+Section: libs
+Architecture: any
+Multi-Arch: same
+Depends: ${misc:Depends}, ${shlibs:Depends}
+Pre-Depends: ${misc:Pre-Depends}
+Description: Library for the Osmocom Community TDMoIP network.
+
+Package: libosmo-octoi-dev
+Section: libdevel
+Architecture: any
+Multi-Arch: same
+Depends: ${misc:Depends}, ${shlibs:Depends},
+ libosmo-octoi0 (= ${binary:Version}),
+ libosmocore-dev,
+Description: Development headers for the Osmocom Community TDMoIP library.
diff --git a/debian/libosmo-octoi-dev.install b/debian/libosmo-octoi-dev.install
new file mode 100644
index 0000000..ac05774
--- /dev/null
+++ b/debian/libosmo-octoi-dev.install
@@ -0,0 +1,5 @@
+usr/include/osmocom/octoi
+usr/lib/*/libosmo-octoi*.a
+usr/lib/*/libosmo-octoi*.so
+usr/lib/*/libosmo-octoi*.la
+usr/lib/*/pkgconfig/libosmo-octoi.pc
diff --git a/debian/libosmo-octoi0.install b/debian/libosmo-octoi0.install
new file mode 100644
index 0000000..389c462
--- /dev/null
+++ b/debian/libosmo-octoi0.install
@@ -0,0 +1 @@
+usr/lib/*/libosmo-octoi*.so.*
diff --git a/include/Makefile.am b/include/Makefile.am
index e74dd6e..3c4cb0d 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -1 +1,2 @@
-nobase_include_HEADERS = osmocom/e1d/proto_clnt.h osmocom/e1d/proto.h osmocom/e1d/proto_srv.h
+nobase_include_HEADERS = osmocom/e1d/proto_clnt.h osmocom/e1d/proto.h osmocom/e1d/proto_srv.h \
+ osmocom/octoi/octoi.h osmocom/octoi/e1oip_proto.h
diff --git a/include/osmocom/e1d/proto.h b/include/osmocom/e1d/proto.h
index 3033f11..a5cb8ec 100644
--- a/include/osmocom/e1d/proto.h
+++ b/include/osmocom/e1d/proto.h
@@ -73,6 +73,7 @@
E1DP_LMODE_OFF = 0x00,
E1DP_LMODE_CHANNELIZED = 0x20,
E1DP_LMODE_SUPERCHANNEL = 0x21,
+ E1DP_LMODE_E1OIP = 0x22,
};

enum osmo_e1dp_ts_mode {
diff --git a/include/osmocom/octoi/e1oip_proto.h b/include/osmocom/octoi/e1oip_proto.h
new file mode 100644
index 0000000..96bc1d7
--- /dev/null
+++ b/include/osmocom/octoi/e1oip_proto.h
@@ -0,0 +1,126 @@
+#pragma once
+
+struct e1oip_hdr {
+ uint8_t version:4,
+ flags:4;
+ uint8_t msg_type; /* enum e1oip_msgtype */
+} __attribute__ ((packed));
+
+#define E1OIP_VERSION 1
+
+enum e1oip_msgtype {
+ E1OIP_MSGT_ECHO_REQ = 0, /* struct e1oip_echo */
+ E1OIP_MSGT_ECHO_RESP = 1, /* struct e1oip_echo */
+ E1OIP_MSGT_TDM_DATA = 2, /* struct e1oip_tdm_hdr + payload */
+ E1OIP_MSGT_SERVICE_REQ = 3, /* struct e1oip_service_req */
+ E1OIP_MSGT_SERVICE_ACK = 4, /* struct e1oip_service_ack */
+ E1OIP_MSGT_SERVICE_REJ = 5, /* struct e1oip_service_rej */
+ E1OIP_MSGT_REDIR_CMD = 6, /* struct e1oip_redir_cmd */
+ E1OIP_MSGT_AUTH_REQ = 7, /* struct e1oip_auth_req */
+ E1OIP_MSGT_AUTH_RESP = 8, /* struct e1oip_auth_resp */
+ E1OIP_MSGT_ERROR_IND = 9, /* struct e1oip_error_ind */
+};
+
+enum e1oip_service {
+ E1OIP_SERVICE_NONE = 0,
+ E1OIP_SERVICE_E1_FRAMED = 1, /* single (framed) E1 trunk */
+};
+
+/* ECHO REQ + ECHO RESP */
+struct e1oip_echo {
+ /* sequence number to distinguish subsequent requests and
+ * responses */
+ uint16_t seq_nr;
+ /* data chosen by sender of ECHO_REQ, echoed back in ECHO_RESP */
+ uint8_t data[0];
+} __attribute__ ((packed));
+
+
+/* follows e1oip_hdr for E1OIP_MSGT_TDM_DATA */
+struct e1oip_tdm_hdr {
+ /* reduced frame number, increments with every E1 frame (8000
+ * Hz). 16bit provides > 8s of wrap-around time, which is more
+ * than sufficient for detecting re-ordered framses over any
+ * meaningful interval */
+ uint16_t frame_nr;
+ /* bit-mask of timeslots with data contained in 'data' below*/
+ uint32_t ts_mask;
+ /* timeslot data: array of bytes per frame; each frame having
+ * one octet for the active timeslots indicated in 'ts_mask',
+ * in ascending order. If ts_mask == 0, then we have a single
+ * octet in 'data' specifying the number of frames expressed in
+ * this packet. */
+ uint8_t data[0];
+} __attribute__ ((packed));
+
+/* client says "hello" to server + requests a service */
+struct e1oip_service_req {
+ uint32_t requested_service;
+ char subscriber_id[32];
+ char software_id[32];
+ char software_version[16];
+ uint32_t capability_flags;
+} __attribute__ ((packed));
+
+/* server instructs client to use other server IP/port */
+struct e1oip_redir_cmd {
+ char server_ip[40]; /* IPv4 or IPv6 */
+ uint16_t server_port; /* UDP port number */
+} __attribute__ ((packed));
+
+/* server requests client to authenticate */
+struct e1oip_auth_req {
+ uint8_t rand_len;
+ uint8_t rand[16];
+ uint8_t autn_len;
+ uint8_t autn[16];
+} __attribute__ ((packed));
+
+/* client responds to auth request.
+ * - res_len == 0 && auts_len == 0 -> failure
+ * - res_len != 0 && auts_len == 0 -> success
+ * - res_len == 0 && auts_len != 0 -> re-sync */
+struct e1oip_auth_resp {
+ uint8_t res_len; /* RES in success case */
+ uint8_t res[16];
+ uint8_t auts_len; /* AUTS in resync case */
+ uint8_t auts[16];
+} __attribute__ ((packed));
+
+/* server acknowledges a client "hello" + service granted */
+struct e1oip_service_ack {
+ uint32_t assigned_service;
+ char server_id[32];
+ char software_id[32];
+ char software_version[16];
+ uint32_t capability_flags; /* server supported capabilities */
+} __attribute__ ((packed));
+
+/* server acknowledges a client "hello" + service rejected */
+struct e1oip_service_rej {
+ uint32_t rejected_service;
+ char reject_message[64];
+} __attribute__ ((packed));
+
+/* either side informs the other of an erroneous condition */
+struct e1oip_error_ind {
+ uint32_t cause;
+ char error_message[64];
+ uint8_t original_message[0];
+} __attribute__ ((packed));
+
+
+struct e1oip_msg {
+ struct e1oip_hdr hdr;
+ union {
+ struct e1oip_echo echo;
+ struct e1oip_tdm_hdr tdm_hdr;
+ struct e1oip_service_req service_req;
+ struct e1oip_redir_cmd redir_cmd;
+ struct e1oip_auth_req auth_req;
+ struct e1oip_auth_resp auth_resp;
+ struct e1oip_service_ack service_ack;
+ struct e1oip_service_rej service_rej;
+ struct e1oip_error_ind error_ind;
+ } u;
+} __attribute__ ((packed));
diff --git a/include/osmocom/octoi/octoi.h b/include/osmocom/octoi/octoi.h
new file mode 100644
index 0000000..48ec122
--- /dev/null
+++ b/include/osmocom/octoi/octoi.h
@@ -0,0 +1,87 @@
+#pragma once
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/vty/vty.h>
+
+struct octoi_peer;
+
+enum octoi_account_mode {
+ ACCOUNT_MODE_NONE,
+ ACCOUNT_MODE_ICE1USB,
+ ACCOUNT_MODE_REDIRECT,
+ ACCOUNT_MODE_DAHDI,
+};
+
+extern const struct value_string octoi_account_mode_name[];
+
+/* a single user account connecting via OCTOI protocol */
+struct octoi_account {
+ struct llist_head list; /* member in octoi_server.cfg.accounts */
+ char *user_id; /* user ID (IMSI) */
+ enum octoi_account_mode mode;
+ union {
+ struct {
+ char *usb_serial; /* USB serial string (ASCII) of icE1usb */
+ uint8_t line_nr; /* line nubmer inside icE1usb */
+ } ice1usb;
+ struct {
+ struct osmo_sockaddr_str to; /* remote IP/port to which to redirect */
+ } redirect;
+ struct {
+ /* TBD */
+ } dahdi;
+ } u;
+};
+
+struct octoi_sock;
+
+struct octoi_server {
+ struct octoi_sock *sock; /* OCTOI UDP server sock representation */
+ struct {
+ struct llist_head accounts; /* list of octoi_account */
+ struct osmo_sockaddr_str local; /* local socket bind address/port */
+ } cfg;
+
+};
+
+struct octoi_client {
+ struct llist_head list; /* member in e1_daemon.octoi.clients */
+ struct octoi_sock *sock; /* OCTOI UDP server sock representation */
+ struct {
+ struct osmo_sockaddr_str remote; /* remote socket address/port */
+ struct osmo_sockaddr_str local; /* local socket bind address/port */
+
+ struct octoi_account *account;
+ } cfg;
+};
+
+/* call-backs from OCTOI library to application */
+struct octoi_ops {
+ /* server notifies the application that a new client connection has just been accepted */
+ void * (*client_connected)(struct octoi_server *srv, struct octoi_peer *peer,
+ struct octoi_account *acc);
+ /* OCTOI library notifies the application that a given peer has disconnected */
+ void (*peer_disconnected)(struct octoi_peer *peer);
+};
+
+struct octoi_daemon {
+ void *priv;
+ const struct octoi_ops *ops;
+ struct octoi_server *server;
+ struct llist_head clients;
+};
+
+extern struct octoi_daemon *g_octoi;
+
+void octoi_init(void *ctx, void *priv, const struct octoi_ops *ops);
+
+int octoi_vty_go_parent(struct vty *vty);
+
+struct octoi_peer *octoi_client_get_peer(struct octoi_client *client);
+void octoi_clnt_start_for_peer(struct octoi_peer *peer, struct octoi_account *acc);
+
+void octoi_peer_e1o_in(struct octoi_peer *peer, const uint8_t *buf, int ftr);
+void octoi_peer_e1t_out(struct octoi_peer *peer, uint8_t *buf, int fts);
+
diff --git a/libosmo-octoi.pc.in b/libosmo-octoi.pc.in
new file mode 100644
index 0000000..94c6a79
--- /dev/null
+++ b/libosmo-octoi.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: Osmocom Community TDMoIP Library
+Description: C Utility Library
+Version: @VERSION@
+Requires: libosmocore
+Libs: -L${libdir} -losmo-octoi
+Cflags: -I${includedir}/
diff --git a/src/Makefile.am b/src/Makefile.am
index f4af9f2..5795598 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,3 +1,5 @@
+SUBDIRS = octoi .
+
# This is _NOT_ the library release version, it's an API version.
# Please read Chapter 6 "Library interface versions" of the libtool
# documentation before making any modification
@@ -47,10 +49,12 @@
usb.c \
vpair.c \
vty.c \
+ e1oip.c \
$(NULL)

osmo_e1d_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) \
- $(LIBOSMOUSB_LIBS) $(LIBUSB_LIBS) libosmo-e1d.la
+ $(LIBOSMOUSB_LIBS) $(LIBUSB_LIBS) libosmo-e1d.la \
+ octoi/libosmo-octoi.la

osmo_e1d_pipe_SOURCES = \
e1d-ts-pipe.c \
@@ -60,7 +64,8 @@


osmo_e1gen_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) \
- $(LIBOSMOUSB_LIBS) $(LIBUSB_LIBS)
+ $(LIBOSMOUSB_LIBS) $(LIBUSB_LIBS) \
+ octoi/libosmo-octoi.la

osmo_e1gen_SOURCES = \
intf_line.c \
diff --git a/src/ctl.c b/src/ctl.c
index 7040001..b5fdee4 100644
--- a/src/ctl.c
+++ b/src/ctl.c
@@ -71,6 +71,9 @@
case E1_LINE_MODE_SUPERCHANNEL:
li->cfg.mode = E1DP_LMODE_SUPERCHANNEL;
break;
+ case E1_LINE_MODE_E1OIP:
+ li->cfg.mode = E1DP_LMODE_E1OIP;
+ break;
default:
OSMO_ASSERT(0);
}
diff --git a/src/e1d.h b/src/e1d.h
index dbcc047..726696e 100644
--- a/src/e1d.h
+++ b/src/e1d.h
@@ -31,8 +31,13 @@
#include <osmocom/core/timer.h>
#include <osmocom/vty/command.h>

+#include <osmocom/octoi/octoi.h>
#include <osmocom/e1d/proto.h>

+/***********************************************************************
+ * core e1d related data structures
+ ***********************************************************************/
+
enum e1d_vty_node {
E1D_NODE = _LAST_OSMOVTY_NODE + 1,
INTF_NODE,
@@ -94,6 +99,8 @@
/* 1 channel group spanning all 31 TS, as used e.g. when using Frame Relay
* or raw HDLC over channelized E1. */
E1_LINE_MODE_SUPERCHANNEL,
+ /* E1 forwarding over IP */
+ E1_LINE_MODE_E1OIP,
};

#define E1L_TS0_RX_CRC4_ERR 0x01
@@ -114,6 +121,7 @@
struct e1_ts ts[32];
/* superchannel */
struct e1_ts superchan;
+ struct octoi_peer *octoi_peer;

struct {
/*! buffer where we aggregate the E bits each multi-frame */
@@ -165,6 +173,7 @@
struct llist_head interfaces;
};

+extern const struct octoi_ops e1d_octoi_ops;

struct e1_line *
e1_intf_find_line(struct e1_intf *intf, uint8_t id);
@@ -194,6 +203,9 @@
void
e1_line_destroy(struct e1_line *line);

+void
+e1_line_active(struct e1_line *line);
+
int
e1_line_mux_out(struct e1_line *line, uint8_t *buf, int fts);

@@ -211,3 +223,12 @@

struct e1_intf *
e1d_vpair_intf_peer(struct e1_intf *intf);
+
+int
+e1oip_line_demux_in(struct e1_line *line, const uint8_t *buf, int ftr);
+
+int
+e1oip_line_mux_out(struct e1_line *line, uint8_t *buf, int fts);
+
+int
+e1d_vty_go_parent(struct vty *vty);
diff --git a/src/e1oip.c b/src/e1oip.c
new file mode 100644
index 0000000..384a91b
--- /dev/null
+++ b/src/e1oip.c
@@ -0,0 +1,152 @@
+/*
+ * e1oip.c
+ *
+ * (C) 2022 by Harald Welte <laforge@osmocom.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include <talloc.h>
+
+#include <osmocom/core/isdnhdlc.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/octoi/octoi.h>
+
+#include "e1d.h"
+#include "log.h"
+
+/***********************************************************************
+ * internal helper functions
+ ***********************************************************************/
+
+/* convenience helper function finding a e1_line for given serial_str + id */
+static struct e1_line *
+find_line_by_usb_serial(struct e1_daemon *e1d, const char *serial_str, uint8_t id)
+{
+ struct e1_intf *e1i = e1d_find_intf_by_usb_serial(e1d, serial_str);
+ if (!e1i)
+ return NULL;
+ return e1_intf_find_line(e1i, id);
+}
+
+static struct e1_line *
+find_line_for_account(struct e1_daemon *e1d, const struct octoi_account *acc)
+{
+ switch (acc->mode) {
+ case ACCOUNT_MODE_ICE1USB:
+ return find_line_by_usb_serial(e1d, acc->u.ice1usb.usb_serial,
+ acc->u.ice1usb.line_nr);
+ case ACCOUNT_MODE_DAHDI:
+ OSMO_ASSERT(0); /* TODO */
+ break;
+ default:
+ return NULL;
+ }
+}
+
+
+/***********************************************************************
+ * e1d integration
+ ***********************************************************************/
+
+/* physical E1 interface has received some E1 frames (E1->IP) */
+int
+e1oip_line_demux_in(struct e1_line *line, const uint8_t *buf, int ftr)
+{
+ OSMO_ASSERT(line->mode == E1_LINE_MODE_E1OIP);
+
+ if (!line->octoi_peer)
+ return -ENODEV;
+
+ octoi_peer_e1o_in(line->octoi_peer, buf, ftr);
+
+ return 0;
+}
+
+/* physical E1 interface needs some E1 fames (E1<-IP) */
+int
+e1oip_line_mux_out(struct e1_line *line, uint8_t *buf, int fts)
+{
+ OSMO_ASSERT(line->mode == E1_LINE_MODE_E1OIP);
+
+ if (!line->octoi_peer) {
+ memset(buf, 0xff, 32*fts);
+ return -ENODEV;
+ }
+
+ octoi_peer_e1t_out(line->octoi_peer, buf, fts);
+
+ return 0;
+}
+
+
+/* OCTOI server FSM has detected an (authenticated) client connection */
+static void *
+_e1d_octoi_client_connected_cb(struct octoi_server *srv, struct octoi_peer *peer,
+ struct octoi_account *acc)
+{
+ struct e1_daemon *e1d = g_octoi->priv;
+ struct e1_line *line;
+
+ /* resolve the line for the just-connected subscriber account */
+ line = find_line_for_account(e1d, acc);
+ if (!line) {
+ LOGP(DE1D, LOGL_NOTICE, "Could not find E1 line for client %s\n",
+ acc->user_id);
+ return NULL;
+ }
+
+ if (line->octoi_peer) {
+ LOGPLI(line, DE1D, LOGL_NOTICE, "New OCTOI client connection for %s, "
+ "but we already have a client connection!\n", acc->user_id);
+ /* FIXME: properly get rid of the old client */
+ }
+ line->octoi_peer = peer;
+
+ LOGPLI(line, DE1D, LOGL_INFO, "New OCTOI client connection for %s\n", acc->user_id);
+
+ return line;
+}
+
+/* OCTOI has detected that a given peer has vanished; delete reference to it */
+static void
+_e1d_octoi_peer_disconnected_cb(struct octoi_peer *peer)
+{
+ struct e1_daemon *e1d = g_octoi->priv;
+ struct e1_intf *intf;
+
+ llist_for_each_entry(intf, &e1d->interfaces, list) {
+ struct e1_line *line;
+ llist_for_each_entry(line, &intf->lines, list) {
+ if (line->octoi_peer == peer) {
+ LOGPLI(line, DE1D, LOGL_NOTICE, "Peer disconnected\n");
+ line->octoi_peer = NULL;
+ return;
+ }
+ }
+ }
+}
+
+const struct octoi_ops e1d_octoi_ops = {
+ .client_connected = &_e1d_octoi_client_connected_cb,
+ .peer_disconnected = &_e1d_octoi_peer_disconnected_cb,
+};
diff --git a/src/intf_line.c b/src/intf_line.c
index bd7a393..9b33141 100644
--- a/src/intf_line.c
+++ b/src/intf_line.c
@@ -40,6 +40,7 @@

#include "e1d.h"
#include "log.h"
+#include <osmocom/octoi/octoi.h>

const struct value_string e1_driver_names[] = {
{ E1_DRIVER_USB, "usb" },
@@ -250,18 +251,66 @@
line->ctrs = rate_ctr_group_alloc(line, &line_ctrg_desc, intf->id << 8 | line->id);
OSMO_ASSERT(line->ctrs);

+ llist_add_tail(&line->list, &intf->lines);
+
+ LOGPLI(line, DE1D, LOGL_NOTICE, "Created\n");
+
+ return line;
+}
+
+/* find an octoi client (if any) for the given line */
+static struct octoi_client *octoi_client_by_line(struct e1_line *line)
+{
+ struct octoi_client *clnt;
+
+ llist_for_each_entry(clnt, &g_octoi->clients, list) {
+ struct octoi_account *acc = clnt->cfg.account;
+ switch (acc->mode) {
+ case ACCOUNT_MODE_ICE1USB:
+ if (!strcmp(line->intf->usb.serial_str, acc->u.ice1usb.usb_serial) &&
+ line->id == acc->u.ice1usb.line_nr)
+ return clnt;
+ break;
+ case ACCOUNT_MODE_NONE:
+ case ACCOUNT_MODE_REDIRECT:
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ }
+ return NULL;
+}
+
+/* mark given line as 'active' (hardware present + enabled) */
+void
+e1_line_active(struct e1_line *line)
+{
+ struct octoi_client *clnt;
+
+ LOGPLI(line, DE1D, LOGL_NOTICE, "Activated\n");
+
osmo_timer_setup(&line->ts0.timer, _ts0_tmr_cb, line);
osmo_timer_schedule(&line->ts0.timer, 1, 0);

- llist_add_tail(&line->list, &intf->lines);
-
/* start watchdog timer */
osmo_timer_setup(&line->watchdog.timer, line_watchdog_cb, line);
osmo_timer_schedule(&line->watchdog.timer, 1, 0);

- LOGPLI(line, DE1D, LOGL_NOTICE, "Created\n");
-
- return line;
+ switch (line->mode) {
+ case E1_LINE_MODE_E1OIP:
+ OSMO_ASSERT(!line->octoi_peer);
+ /* find a client for this line */
+ clnt = octoi_client_by_line(line);
+ if (!clnt)
+ return;
+ /* start the peer for this client */
+ line->octoi_peer = octoi_client_get_peer(clnt);
+ OSMO_ASSERT(line->octoi_peer);
+ octoi_clnt_start_for_peer(line->octoi_peer, clnt->cfg.account);
+ break;
+ default:
+ break;
+ }
}

void
diff --git a/src/mux_demux.c b/src/mux_demux.c
index 3d72f7f..671d6d1 100644
--- a/src/mux_demux.c
+++ b/src/mux_demux.c
@@ -211,6 +211,9 @@
case E1_LINE_MODE_SUPERCHANNEL:
_e1_line_mux_out_superchan(line, buf, fts);
break;
+ case E1_LINE_MODE_E1OIP:
+ e1oip_line_mux_out(line, buf, fts);
+ break;
default:
OSMO_ASSERT(0);
}
@@ -423,6 +426,8 @@
return _e1_line_demux_in_channelized(line, buf, ftr);
case E1_LINE_MODE_SUPERCHANNEL:
return _e1_line_demux_in_superchan(line, buf, ftr);
+ case E1_LINE_MODE_E1OIP:
+ return e1oip_line_demux_in(line, buf, ftr);
default:
OSMO_ASSERT(0);
}
diff --git a/src/octoi/Makefile.am b/src/octoi/Makefile.am
new file mode 100644
index 0000000..b77e0b8
--- /dev/null
+++ b/src/octoi/Makefile.am
@@ -0,0 +1,37 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall -Wextra -Wno-missing-field-initializers -Wno-unused-parameter -Wno-unused-result $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS)
+
+# This is _NOT_ the library release version, it's an API version.
+# Please read Chapter 6 "Library interface versions" of the libtool
+# documentation before making any modification
+LIBVERSION=0:0:0
+
+lib_LTLIBRARIES = libosmo-octoi.la
+
+libosmo_octoi_la_SOURCES = \
+ frame_fifo.c \
+ e1oip.c \
+ octoi.c \
+ octoi_sock.c \
+ octoi_fsm.c \
+ octoi_srv_fsm.c \
+ octoi_srv_vty.c \
+ octoi_clnt_fsm.c \
+ octoi_clnt_vty.c \
+ $(NULL)
+
+libosmo_octoi_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined \
+ -Wl,--version-script=$(srcdir)/libosmo-octoi.map
+libosmo_octoi_la_LIBADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS)
+
+noinst_HEADERS = \
+ e1oip.h \
+ frame_fifo.h \
+ octoi.h \
+ octoi_fsm.h \
+ octoi_sock.h \
+ octoi_vty.h \
+ $(NULL)
+
+EXTRA_DIST = libosmo-octoi.map
diff --git a/src/octoi/e1oip.c b/src/octoi/e1oip.c
new file mode 100644
index 0000000..0cb7206
--- /dev/null
+++ b/src/octoi/e1oip.c
@@ -0,0 +1,281 @@
+/*
+ * e1oip.c - Actual TDM/E1oIP handling within OCTOI
+ *
+ * (C) 2022 by Harald Welte <laforge@osmocom.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <time.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <talloc.h>
+
+#include <osmocom/core/isdnhdlc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/sockaddr_str.h>
+
+#include <osmocom/octoi/e1oip_proto.h>
+
+#include "octoi_sock.h"
+#include "frame_fifo.h"
+#include "e1oip.h"
+
+static const struct rate_ctr_desc iline_ctr_description[] = {
+ [LINE_CTR_E1oIP_UNDERRUN] = { "e1oip:underrun", "Frames missing/substituted in IP->E1 direction"},
+ [LINE_CTR_E1oIP_OVERFLOW] = { "e1oip:overflow", "Frames overflowed in IP->E1 direction"},
+};
+
+static const struct rate_ctr_group_desc iline_ctrg_desc = {
+ .group_name_prefix = "e1oip_line",
+ .group_description = "Counters for E1oIP line",
+ .class_id = OSMO_STATS_CLASS_GLOBAL,
+ .num_ctr = ARRAY_SIZE(iline_ctr_description),
+ .ctr_desc = iline_ctr_description,
+};
+
+static const struct osmo_stat_item_desc iline_stat_description[] = {
+ [LINE_STAT_E1oIP_RTT] = { "e1oip:rtt", "Round Trip Time (in ms)" },
+ [LINE_STAT_E1oIP_E1O_FIFO] = { "e1oip:e1o_fifo_level", "E1 originated FIFO level" },
+ [LINE_STAT_E1oIP_E1T_FIFO] = { "e1oip:e1t_fifo_level", "E1 terminated FIFO level" },
+};
+
+static const struct osmo_stat_item_group_desc iline_stats_desc = {
+ .group_name_prefix = "e1oip_line",
+ .group_description = "Stat items for E1oIP line",
+ .class_id = OSMO_STATS_CLASS_GLOBAL,
+ .num_items = ARRAY_SIZE(iline_stat_description),
+ .item_desc = iline_stat_description,
+};
+
+
+/* E1 -> IP FIFO threshold reached: send one packet */
+static void fifo_threshold_cb(struct frame_fifo *fifo, unsigned int frames, void *priv)
+{
+ struct e1oip_line *iline = priv;
+ struct msgb *msg;
+ struct e1oip_tdm_hdr *eith;
+ unsigned int n_frames = fifo->threshold;
+ uint8_t buf[n_frames][BYTES_PER_FRAME];
+ const uint8_t *ref_frame;
+ uint32_t ts_mask = 0;
+ unsigned int num_ts = 0;
+ unsigned int i;
+ uint8_t *cur;
+ int rc;
+
+ msg = msgb_alloc_c(iline, 2048, "E1oIP UDP Tx");
+ OSMO_ASSERT(msg);
+ eith = (struct e1oip_tdm_hdr *) msgb_put(msg, sizeof(*eith));
+
+ eith->frame_nr = htons(iline->e1o.next_seq);
+
+ //printf("%s: n_frames=%u\n", __func__, n_frames);
+
+ /* first pull all the frames to a local buffer */
+ for (i = 0; i < n_frames; i++) {
+ rc = frame_fifo_out(&iline->e1o.fifo, buf[i]);
+ if (rc < 0) {
+ /* this situation cannot really happen: The FIFO called us that
+ * a certain threshold is reached, ubt now it cannot provide
+ * frames? */
+ LOGPEER(iline->peer, LOGL_ERROR,
+ "frame_fifo_out failure for frame %u/%u\n", iline->e1o.next_seq + i, i);
+ }
+ }
+ iline_stat_set(iline, LINE_STAT_E1oIP_E1O_FIFO, frame_fifo_frames(&iline->e1o.fifo));
+
+ /* then compute the ts_mask */
+ for (i = 0, ref_frame = iline->e1o.last_frame; i < n_frames; i++, ref_frame = buf[i-1]) {
+ /* FIXME: what to do about TS0? */
+ for (unsigned int j = 1; j < BYTES_PER_FRAME; j++) {
+ if (buf[i][j] != ref_frame[j])
+ ts_mask |= (1U << j);
+ }
+ }
+ eith->ts_mask = htonl(ts_mask);
+
+ for (i = 0; i < BYTES_PER_FRAME; i++) {
+ if (ts_mask & (1U << i))
+ num_ts++;
+ }
+
+ /* finally, encode the payload */
+ if (num_ts == 0) {
+ /* explicitly encode the number of frames, as the receiver can not determine it
+ * if we don't include any data */
+ msgb_put_u8(msg, n_frames);
+ } else {
+ cur = msgb_put(msg, num_ts * n_frames);
+ for (i = 0; i < n_frames; i++) {
+ for (unsigned int j = 0; j < BYTES_PER_FRAME; j++) {
+ if (ts_mask & (1U << j))
+ *cur++ = buf[i][j];
+ }
+ }
+ }
+
+ /* send the packet to the peer */
+ octoi_tx(iline->peer, E1OIP_MSGT_TDM_DATA, 0, msgb_data(msg), msgb_length(msg));
+ msgb_free(msg);
+
+ /* update the local state */
+ iline->e1o.next_seq += n_frames;
+ if (n_frames)
+ memcpy(iline->e1o.last_frame, buf[n_frames-1], BYTES_PER_FRAME);
+}
+
+/* build a table indexed by offset inside the EoIP TDM frame resulting in TS number */
+static unsigned int ts_mask2idx(uint8_t *out, uint32_t ts_mask)
+{
+ unsigned int i;
+ uint8_t *cur = out;
+
+ memset(out, 0xff, BYTES_PER_FRAME);
+
+ for (i = 0; i < BYTES_PER_FRAME; i++) {
+ if (ts_mask & (1U << i))
+ *cur++ = i;
+ }
+
+ return (cur - out);
+}
+
+/* An E1OIP_MSGT_TDM_DATA message was received from a remote IP peer */
+int e1oip_rcvmsg_tdm_data(struct e1oip_line *iline, struct msgb *msg)
+{
+ struct octoi_peer *peer = iline->peer;
+ const struct e1oip_tdm_hdr *e1th;
+ uint16_t frame_nr;
+ uint32_t ts_mask;
+ uint8_t idx2ts[BYTES_PER_FRAME];
+ unsigned int n_frames;
+ uint8_t frame_buf[BYTES_PER_FRAME];
+ unsigned int num_ts;
+ struct timespec ts;
+
+ /* update the timestamp at which we last received data from this peer */
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ peer->last_rx_tdm = ts.tv_sec;
+
+ if (!peer->tdm_permitted)
+ return -EPERM;
+
+ if (msgb_l2len(msg) < sizeof(*e1th))
+ return -EINVAL;
+
+ /* read header */
+ e1th = (const struct e1oip_tdm_hdr *) msgb_l2(msg);
+ msg->l3h = msgb_l2(msg) + sizeof(*e1th);
+ frame_nr = ntohs(e1th->frame_nr);
+ ts_mask = ntohl(e1th->ts_mask);
+
+ if (frame_nr != iline->e1t.next_seq) {
+ LOGPEER(peer, LOGL_NOTICE, "RxIP: frame_nr=%u, but expected %u: packet loss? "
+ "or re-ordering?\n", frame_nr, iline->e1t.next_seq);
+ /* FIXME: locally substitute frames? */
+ }
+
+ /* compute E1oIP idx to timeslot table */
+ num_ts = ts_mask2idx(idx2ts, ts_mask);
+ if (num_ts > 0) {
+ n_frames = msgb_l3len(msg) / num_ts;
+ if (msgb_l3len(msg) % num_ts) {
+ LOGPEER(peer, LOGL_NOTICE,
+ "RxIP: %u extraneous bytes (len=%u, num_ts=%u, n_frames=%u)\n",
+ msgb_length(msg) % num_ts, msgb_length(msg), num_ts, n_frames);
+ }
+ LOGPEER(peer, LOGL_INFO, "RxIP: frame=%05u ts_mask=0x%08x num_ts=%02u, n_frames=%u\n",
+ frame_nr, ts_mask, num_ts, n_frames);
+ } else {
+ if (msgb_l3len(msg) < 1) {
+ LOGPEER(peer, LOGL_ERROR, "RxIP: num_ts==0 but no n_frames octet!\n");
+ n_frames = BYTES_PER_FRAME; /* hackish assumption */
+ } else
+ n_frames = msg->l3h[0];
+ }
+
+ memcpy(frame_buf, iline->e1t.last_frame, BYTES_PER_FRAME);
+ for (unsigned int i = 0; i < n_frames; i++) {
+ for (unsigned int j = 0; j < num_ts; j++) {
+ uint8_t ts_nr = idx2ts[j];
+ frame_buf[ts_nr] = e1th->data[i*num_ts + j];
+ }
+ /* FIXME: what to do about TS0? */
+ frame_fifo_in(&iline->e1t.fifo, frame_buf);
+ }
+ /* update local state */
+ memcpy(iline->e1t.last_frame, frame_buf, BYTES_PER_FRAME);
+ iline->e1t.next_seq = frame_nr + n_frames;
+
+ iline_stat_set(iline, LINE_STAT_E1oIP_E1T_FIFO, frame_fifo_frames(&iline->e1t.fifo));
+
+ return 0;
+}
+
+/* TODO: more meaningful identifiers? */
+static int g_ctr_idx = 0;
+
+struct e1oip_line *e1oip_line_alloc(struct octoi_peer *peer)
+{
+ struct e1oip_line *iline;
+ int ctr_idx = g_ctr_idx++;
+
+ if (peer->iline)
+ return NULL;
+
+ iline = talloc_zero(peer, struct e1oip_line);
+ if (!iline)
+ return NULL;
+
+ iline->ctrs = rate_ctr_group_alloc(iline, &iline_ctrg_desc, ctr_idx);
+ iline->stats = osmo_stat_item_group_alloc(iline, &iline_stats_desc, ctr_idx);
+
+ iline->cfg.batching_factor = 32;
+ iline->cfg.prefill_frame_count = 400; /* 50ms */
+
+ frame_fifo_init(&iline->e1o.fifo, iline->cfg.batching_factor, fifo_threshold_cb, iline);
+ memset(&iline->e1o.last_frame, 0xff, sizeof(iline->e1o.last_frame));
+
+ frame_fifo_init(&iline->e1t.fifo, 0, NULL, iline);
+ memset(&iline->e1t.last_frame, 0xff, sizeof(iline->e1o.last_frame));
+
+ iline->peer = peer;
+ peer->iline = iline;
+
+ return iline;
+}
+
+void e1oip_line_destroy(struct e1oip_line *iline)
+{
+ if (!iline)
+ return;
+
+ rate_ctr_group_free(iline->ctrs);
+ osmo_stat_item_group_free(iline->stats);
+ if (iline->peer)
+ iline->peer->iline = NULL;
+ iline->peer = NULL;
+ talloc_free(iline);
+}
diff --git a/src/octoi/e1oip.h b/src/octoi/e1oip.h
new file mode 100644
index 0000000..e08e3ca
--- /dev/null
+++ b/src/octoi/e1oip.h
@@ -0,0 +1,61 @@
+#pragma once
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stat_item.h>
+
+#include <osmocom/octoi/e1oip_proto.h>
+
+#include "frame_fifo.h"
+
+#define iline_ctr_add(iline, idx, add) rate_ctr_add(rate_ctr_group_get_ctr((iline)->ctrs, idx), add)
+#define iline_stat_set(iline, idx, add) \
+ osmo_stat_item_set(osmo_stat_item_group_get_item((iline)->stats, idx), add)
+
+enum e1oip_line_ctr {
+ LINE_CTR_E1oIP_UNDERRUN,
+ LINE_CTR_E1oIP_OVERFLOW,
+};
+
+enum e1oip_line_stat {
+ LINE_STAT_E1oIP_RTT,
+ LINE_STAT_E1oIP_E1O_FIFO,
+ LINE_STAT_E1oIP_E1T_FIFO,
+};
+
+struct octoi_peer;
+
+struct e1oip_line {
+ /* back-pointer */
+ struct octoi_peer *peer;
+
+ struct rate_ctr_group *ctrs;
+ struct osmo_stat_item_group *stats;
+
+ /* configuration data */
+ struct {
+ uint8_t batching_factor;
+ uint32_t prefill_frame_count;
+ } cfg;
+
+ /* E1 originated side (E1->IP) */
+ struct {
+ struct frame_fifo fifo;
+ uint8_t last_frame[BYTES_PER_FRAME]; /* last frame on the E1 side */
+ uint16_t next_seq;
+ } e1o;
+
+ /* E1 terminated side (E1<-IP) */
+ struct {
+ struct frame_fifo fifo;
+ uint8_t last_frame[BYTES_PER_FRAME]; /* last frame on the E1 side */
+ uint16_t next_seq; /* next expected sequence nr */
+ } e1t;
+
+ /* TODO: statistics (RTT, frame loss, std deviation, alarms */
+};
+
+struct e1oip_line *e1oip_line_alloc(struct octoi_peer *peer);
+void e1oip_line_destroy(struct e1oip_line *iline);
+
+int e1oip_rcvmsg_tdm_data(struct e1oip_line *iline, struct msgb *msg);
diff --git a/src/octoi/frame_fifo.c b/src/octoi/frame_fifo.c
new file mode 100644
index 0000000..6ba6beb
--- /dev/null
+++ b/src/octoi/frame_fifo.c
@@ -0,0 +1,146 @@
+/*
+ * frame_fifo.c
+ *
+ * (C) 2022 by Harald Welte <laforge@osmocom.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <osmocom/core/utils.h>
+
+#include "frame_fifo.h"
+
+
+/***********************************************************************
+ * Frame FIFO
+ ***********************************************************************/
+
+void fifo_dump(struct frame_fifo *fifo)
+{
+ printf("buf=%p, size=%zu, next_in=%lu, next_out=%lu\n", fifo->buf, sizeof(fifo->buf),
+ fifo->next_in - fifo->buf, fifo->next_out - fifo->buf);
+}
+
+/*! Initialize a frame FIFO.
+ * \param fifo Caller-allocated memory for FIFO data structure
+ * \param threshold After how many available frames shall we call threshold_cb
+ * \param threshold_cb Optional call-back to call whenever FIFO contains more than 'threshold' frames
+ * \param priv Opaque pointer passed to threshold_cb */
+void frame_fifo_init(struct frame_fifo *fifo, unsigned int threshold,
+ void (*threshold_cb)(struct frame_fifo *fifo, unsigned int frames, void *priv),
+ void *priv)
+{
+ memset(fifo->buf, 0xff, sizeof(fifo->buf));
+ fifo->next_in = fifo->buf;
+ fifo->next_out = fifo->buf;
+ fifo->threshold = threshold;
+ fifo->threshold_cb = threshold_cb;
+ fifo->priv = priv;
+}
+
+#define FIFO_BUF_END(f) ((f)->buf + sizeof((f)->buf))
+
+/*! put one received frames into the FIFO.
+ * \param fifo The FIFO to which we want to put (append) multiple frames
+ * \param frame Pointer to memory containing the frame data
+ * \param count Number of frames to put into FIFO.
+ * \returns 0 on success; -1 on error (overflow */
+int frame_fifo_in(struct frame_fifo *fifo, const uint8_t *frame)
+{
+ OSMO_ASSERT(fifo->next_in + BYTES_PER_FRAME <= FIFO_BUF_END(fifo));
+
+ memcpy(fifo->next_in, frame, BYTES_PER_FRAME);
+
+ fifo->next_in += BYTES_PER_FRAME;
+ if (fifo->next_in >= FIFO_BUF_END(fifo))
+ fifo->next_in -= sizeof(fifo->buf);
+
+ /* FIXME: detect overflow */
+
+ if (fifo->threshold_cb) {
+ unsigned int frames_avail = frame_fifo_frames(fifo);
+ if (frames_avail >= fifo->threshold)
+ fifo->threshold_cb(fifo, frames_avail, fifo->priv);
+ }
+
+ return 0;
+}
+
+/*! put (append) multiple received frames into the FIFO.
+ * \param fifo The FIFO to which we want to put (append) multiple frames
+ * \param frame Pointer to memory containing the frame data
+ * \param count Number of frames to put into FIFO.
+ * \returns Number of frames actually put to FIFO; can be less than 'count' */
+int frame_fifo_in_multi(struct frame_fifo *fifo, const uint8_t *frame, size_t count)
+{
+ const uint8_t *cur = frame;
+ unsigned int i;
+ int rc;
+
+ for (i = 0; i < count; i++) {
+ rc = frame_fifo_in(fifo, cur);
+ /* abort on the first failing frame, there's no point in trying further */
+ if (rc < 0)
+ return (int) i;
+ cur += BYTES_PER_FRAME;
+ }
+ return (int) i;
+}
+
+/*! pull one frames out of the FIFO.
+ * \param fifo The FIFO from which we want to pull frames
+ * \param out Caller-allocated output buffer
+ * \returns 0 on success; -1 on error (no frame available) */
+int frame_fifo_out(struct frame_fifo *fifo, uint8_t *out)
+{
+ if (frame_fifo_frames(fifo) < 1)
+ return -1;
+ memcpy(out, fifo->next_out, BYTES_PER_FRAME);
+ fifo->next_out += BYTES_PER_FRAME;
+
+ if (fifo->next_out >= FIFO_BUF_END(fifo))
+ fifo->next_out -= sizeof(fifo->buf);
+
+ return 0;
+}
+
+/*! pull multiple frames out of the FIFO.
+ * \param fifo The FIFO from which we want ot pull frames
+ * \param out Caller-allocated output buffer
+ * \param count Number of frames to pull
+ * \returns number of frames pulled; can be 0 or less than count */
+int frame_fifo_out_multi(struct frame_fifo *fifo, uint8_t *out, size_t count)
+{
+ uint8_t *cur = out;
+ unsigned int i;
+ int rc = 0;
+
+ for (i = 0; i < count; i++) {
+ rc = frame_fifo_out(fifo, cur);
+ /* if there's no data in the FIFO, return number of frames
+ * pulled so far, could be 0. */
+ if (rc < 0)
+ return (int) i;
+ cur += BYTES_PER_FRAME;
+ }
+ return (int) i;
+}
diff --git a/src/octoi/frame_fifo.h b/src/octoi/frame_fifo.h
new file mode 100644
index 0000000..64aa06a
--- /dev/null
+++ b/src/octoi/frame_fifo.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#define BYTES_PER_FRAME 32
+#define FRAMES_PER_FIFO 800
+
+struct frame_fifo {
+ uint8_t *next_in; /* where to write next input into FIFO */
+ uint8_t *next_out; /* where to read next output from FIFO */
+ uint8_t threshold; /* number of frames that should trigger */
+ void (*threshold_cb)(struct frame_fifo *fifo, unsigned int frames, void *priv);
+ void *priv;
+
+ uint8_t buf[BYTES_PER_FRAME * FRAMES_PER_FIFO];
+};
+
+void frame_fifo_init(struct frame_fifo *fifo, unsigned int threshold,
+ void (*threshold_cb)(struct frame_fifo *fifo, unsigned int frames, void *priv),
+ void *priv);
+
+/* number of frames currently available in FIFO */
+static inline unsigned int frame_fifo_frames(struct frame_fifo *fifo)
+{
+ return ((fifo->next_in + sizeof(fifo->buf) - fifo->next_out) % sizeof(fifo->buf)) / BYTES_PER_FRAME;
+}
+
+/* for how many frames do we have space in the FIFO? */
+static inline unsigned int frame_fifo_space(struct frame_fifo *fifo)
+{
+ return FRAMES_PER_FIFO - frame_fifo_frames(fifo);
+}
+
+/* put a received frame into the FIFO */
+int frame_fifo_in(struct frame_fifo *fifo, const uint8_t *frame);
+
+/* put multiple received frames into the FIFO */
+int frame_fifo_in_multi(struct frame_fifo *fifo, const uint8_t *frame, size_t count);
+
+/* pull one frame out of the FIFO */
+int frame_fifo_out(struct frame_fifo *fifo, uint8_t *out);
+
+/* pull multiple frames out of the FIFO */
+int frame_fifo_out_multi(struct frame_fifo *fifo, uint8_t *out, size_t count);
diff --git a/src/octoi/libosmo-octoi.map b/src/octoi/libosmo-octoi.map
new file mode 100644
index 0000000..01b5167
--- /dev/null
+++ b/src/octoi/libosmo-octoi.map
@@ -0,0 +1,12 @@
+{
+global:
+g_octoi;
+octoi_vty_go_parent;
+octoi_init;
+octoi_client_get_peer;
+octoi_clnt_start_for_peer;
+octoi_peer_e1o_in;
+octoi_peer_e1t_out;
+
+local: *;
+};
diff --git a/src/octoi/octoi.c b/src/octoi/octoi.c
new file mode 100644
index 0000000..a2c4ca6
--- /dev/null
+++ b/src/octoi/octoi.c
@@ -0,0 +1,131 @@
+/*
+ * octoi.c
+ *
+ * (C) 2022 by Harald Welte <laforge@osmocom.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/octoi/octoi.h>
+
+#include "e1oip.h"
+#include "octoi.h"
+#include "octoi_sock.h"
+#include "frame_fifo.h"
+
+struct octoi_daemon *g_octoi;
+
+static struct octoi_client *client4account(struct octoi_account *acc)
+{
+ struct octoi_client *clnt;
+
+ llist_for_each_entry(clnt, &g_octoi->clients, list) {
+ if (clnt->cfg.account == acc)
+ return clnt;
+ }
+
+ return NULL;
+}
+
+int octoi_vty_go_parent(struct vty *vty)
+{
+ struct octoi_account *acc;
+
+ switch (vty->node) {
+ case OCTOI_ACCOUNT_NODE:
+ vty->node = OCTOI_SRV_NODE;
+ vty->index = g_octoi->server;
+ break;
+ case OCTOI_CLNT_ACCOUNT_NODE:
+ acc = vty->index;
+ vty->node = OCTOI_CLNT_NODE;
+ vty->index = client4account(acc);
+ break;
+ default:
+ vty->node = CONFIG_NODE;
+ vty->index = NULL;
+ break;
+ }
+ return 0;
+}
+
+void octoi_init(void *ctx, void *priv, const struct octoi_ops *ops)
+{
+ OSMO_ASSERT(!g_octoi);
+
+ osmo_fsm_register(&octoi_server_fsm);
+ osmo_fsm_register(&octoi_client_fsm);
+
+ g_octoi = talloc_zero(ctx, struct octoi_daemon);
+ OSMO_ASSERT(g_octoi);
+
+ g_octoi->priv = priv;
+ g_octoi->ops = ops;
+ INIT_LLIST_HEAD(&g_octoi->clients);
+
+ octoi_server_vty_init();
+ octoi_client_vty_init();
+}
+
+/* resolve the octoi_peer for a specified octoi_client */
+struct octoi_peer *octoi_client_get_peer(struct octoi_client *clnt)
+{
+ return octoi_sock_client_get_peer(clnt->sock);
+}
+
+
+/*! E1 interface has received some E1 frames, forward in E1->IP direction.
+ * \param[in] peer The peer for which E1 frames were received
+ * \param[in] buf Buffer holding the just-received E1 frames
+ * \param[in] ftr Number of 32-byte frames in buf */
+void octoi_peer_e1o_in(struct octoi_peer *peer, const uint8_t *buf, int ftr)
+{
+ struct e1oip_line *iline = peer->iline;
+ int rc;
+
+ if (!peer->tdm_permitted)
+ return;
+
+ rc = frame_fifo_in_multi(&iline->e1o.fifo, buf, ftr);
+ if (rc < ftr)
+ iline_ctr_add(iline, LINE_CTR_E1oIP_OVERFLOW, ftr - rc);
+
+ iline_stat_set(iline, LINE_STAT_E1oIP_E1O_FIFO, frame_fifo_frames(&iline->e1o.fifo));
+}
+
+/*! E1 interface needs to transmit some E1 frames, E1<-IP direction.
+ * \param[in] peer The peer from which E1 frames are needed
+ * \param[in] buf Caller-provided output buffer to which frames are written.
+ * \param[in] fts Number of 32-byte frames to be written to buf. */
+void octoi_peer_e1t_out(struct octoi_peer *peer, uint8_t *buf, int fts)
+{
+ struct e1oip_line *iline = peer->iline;
+ int rc;
+
+ if (!peer->tdm_permitted)
+ return;
+
+ for (int i = 0; i < fts; i++) {
+ uint8_t *cur = buf + BYTES_PER_FRAME*i;
+ rc = frame_fifo_out(&iline->e1t.fifo, cur);
+ if (rc < 0) {
+ iline_ctr_add(iline, LINE_CTR_E1oIP_UNDERRUN, 1);
+ /* substitute with last received frame */
+ memcpy(cur, iline->e1t.last_frame, BYTES_PER_FRAME);
+ }
+ }
+ iline_stat_set(iline, LINE_STAT_E1oIP_E1T_FIFO, frame_fifo_frames(&iline->e1t.fifo));
+}
diff --git a/src/octoi/octoi.h b/src/octoi/octoi.h
new file mode 100644
index 0000000..589c208
--- /dev/null
+++ b/src/octoi/octoi.h
@@ -0,0 +1,26 @@
+#pragma once
+
+/***********************************************************************
+ * OCTOI related data structures
+ ***********************************************************************/
+
+#include <osmocom/vty/command.h>
+
+#include <osmocom/octoi/octoi.h>
+
+/* FIXME: migrate to libosmocore/vty/command.h */
+enum octoi_vty_node {
+ OCTOI_SRV_NODE = RESERVED1_NODE,
+ OCTOI_ACCOUNT_NODE,
+ OCTOI_CLNT_NODE,
+ OCTOI_CLNT_ACCOUNT_NODE,
+};
+
+
+extern struct osmo_fsm octoi_server_fsm;
+extern struct osmo_fsm octoi_client_fsm;
+
+void octoi_server_vty_init(void);
+void octoi_client_vty_init(void);
+
+struct octoi_account *octoi_account_find(struct octoi_server *srv, const char *user_id);
diff --git a/src/octoi/octoi_clnt_fsm.c b/src/octoi/octoi_clnt_fsm.c
new file mode 100644
index 0000000..1c38bc8
--- /dev/null
+++ b/src/octoi/octoi_clnt_fsm.c
@@ -0,0 +1,320 @@
+/*
+ * octoi_clnt_fsm.c - OCTOI Client-side Finite State Machine
+ *
+ * (C) 2022 by Harald Welte <laforge@osmocom.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/msgb.h>
+
+#include <osmocom/octoi/octoi.h>
+#include <osmocom/octoi/e1oip_proto.h>
+
+#include "octoi_sock.h"
+#include "octoi_fsm.h"
+#include "e1oip.h"
+
+enum octoi_client_fsm_state {
+ CLNT_ST_INIT,
+ CLNT_ST_SVC_REQ_SENT,
+ CLNT_ST_ACCEPTED,
+ CLNT_ST_REJECTED,
+ CLNT_ST_REDIRECTED,
+};
+
+struct clnt_state {
+ struct octoi_peer *peer;
+
+ /* fields filled in locally */
+ uint32_t service;
+ uint32_t capability_flags;
+ struct osmo_timer_list rx_alive_timer;
+ struct octoi_account *acc;
+
+ /* fields below are all filled in once received from the remote server side */
+ struct {
+ char *server_id;
+ char *software_id;
+ char *software_version;
+ uint32_t capability_flags;
+ } remote;
+};
+
+static void clnt_st_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct clnt_state *st = fi->priv;
+
+ switch (event) {
+ case OCTOI_CLNT_EV_REQUEST_SERVICE:
+ octoi_tx_service_req(st->peer, st->service, st->acc->user_id,
+ PACKAGE_NAME, PACKAGE_VERSION, st->capability_flags);
+ osmo_fsm_inst_state_chg(fi, CLNT_ST_SVC_REQ_SENT, 10, 0);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void clnt_st_svc_req_sent(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct clnt_state *st = fi->priv;
+ struct msgb *msg = NULL;
+ //struct e1oip_auth_req *auth_req = NULL;
+ struct e1oip_service_ack *svc_ack = NULL;
+ struct e1oip_service_rej *svc_rej = NULL;
+ //struct e1oip_redir_cmd *redir_cmd = NULL;
+
+ switch (event) {
+ case OCTOI_CLNT_EV_RX_AUTH_REQ:
+ msg = data;
+ //auth_req = msgb_l2(msg);
+ /* TODO: implement authentication */
+ break;
+ case OCTOI_CLNT_EV_RX_SVC_ACK:
+ msg = data;
+ svc_ack = msgb_l2(msg);
+ osmo_talloc_replace_string(st->peer, &st->remote.server_id, svc_ack->server_id);
+ osmo_talloc_replace_string(st->peer, &st->remote.software_id, svc_ack->software_id);
+ osmo_talloc_replace_string(st->peer, &st->remote.software_version, svc_ack->software_version);
+ LOGPFSML(fi, LOGL_NOTICE, "Rx SERVICE_ACK (service=%u, server_id='%s', software_id='%s', "
+ "software_version='%s'\n", ntohl(svc_ack->assigned_service),
+ st->remote.server_id, st->remote.software_id, st->remote.software_version);
+ osmo_fsm_inst_state_chg(fi, CLNT_ST_ACCEPTED, 0, 0);
+ break;
+ case OCTOI_CLNT_EV_RX_SVC_REJ:
+ msg = data;
+ svc_rej = msgb_l2(msg);
+ LOGPFSML(fi, LOGL_NOTICE, "Rx SERVICE_REJ (service=%u, message='%s')\n",
+ ntohl(svc_rej->rejected_service), svc_rej->reject_message);
+ osmo_fsm_inst_state_chg(fi, CLNT_ST_REJECTED, 0, 0);
+ break;
+ case OCTOI_CLNT_EV_RX_REDIR_CMD:
+ msg = data;
+ //redir_cmd = msgb_l2(msg);
+ LOGPFSML(fi, LOGL_NOTICE, "Rx REDIR_CMD, but not yet supported\n");
+ osmo_fsm_inst_state_chg(fi, CLNT_ST_REDIRECTED, 0, 0);
+ break;
+ }
+}
+
+static void clnt_st_accepted_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct clnt_state *st = fi->priv;
+
+ st->peer->tdm_permitted = true;
+ osmo_timer_schedule(&st->rx_alive_timer, 3, 0);
+}
+
+static void clnt_st_accepted(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct clnt_state *st = fi->priv;
+
+ switch (event) {
+ case OCTOI_CLNT_EV_RX_AUTH_REQ:
+ /* TODO: implement authentication */
+ LOGPFSML(fi, LOGL_NOTICE, "Rx AUTH_REQ, but no authentication supported yet!\n");
+ break;
+ case OCTOI_EV_RX_TDM_DATA:
+ e1oip_rcvmsg_tdm_data(st->peer->iline, data);
+ break;
+ }
+}
+
+static void clnt_st_accepted_onleave(struct osmo_fsm_inst *fi, uint32_t next_state)
+{
+ struct clnt_state *st = fi->priv;
+
+ osmo_timer_del(&st->rx_alive_timer);
+ st->peer->tdm_permitted = false;
+}
+
+static void clnt_st_rejected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ LOGPFSML(fi, LOGL_ERROR, "Server has rejected service, will not retry until program restart\n");
+}
+
+static void clnt_st_redirected(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ //struct clnt_state *st = fi->priv;
+}
+
+
+static const struct osmo_fsm_state client_fsm_states[] = {
+ [CLNT_ST_INIT] = {
+ .name = "INIT",
+ .in_event_mask = S(OCTOI_CLNT_EV_REQUEST_SERVICE),
+ .out_state_mask = S(CLNT_ST_SVC_REQ_SENT),
+ .action = clnt_st_init,
+ },
+ [CLNT_ST_SVC_REQ_SENT] = {
+ .name = "SVC_REQ_SENT",
+ .in_event_mask = S(OCTOI_CLNT_EV_RX_AUTH_REQ) |
+ S(OCTOI_CLNT_EV_RX_SVC_ACK) |
+ S(OCTOI_CLNT_EV_RX_SVC_REJ) |
+ S(OCTOI_CLNT_EV_RX_REDIR_CMD),
+ .out_state_mask = S(CLNT_ST_SVC_REQ_SENT) |
+ S(CLNT_ST_ACCEPTED) |
+ S(CLNT_ST_REJECTED) |
+ S(CLNT_ST_REDIRECTED),
+ .action = clnt_st_svc_req_sent,
+ },
+ [CLNT_ST_ACCEPTED] = {
+ .name = "ACCEPTED",
+ .in_event_mask = S(OCTOI_CLNT_EV_RX_AUTH_REQ) |
+ S(OCTOI_EV_RX_TDM_DATA),
+ .out_state_mask = S(CLNT_ST_INIT),
+ .action = clnt_st_accepted,
+ .onenter = clnt_st_accepted_onenter,
+ .onleave = clnt_st_accepted_onleave,
+ },
+ [CLNT_ST_REJECTED] = {
+ .name = "REJECTED",
+ .in_event_mask = 0,
+ .out_state_mask = 0,
+ .onenter = clnt_st_rejected_onenter,
+ },
+ [CLNT_ST_REDIRECTED] = {
+ .name = "REDIRECTED",
+ .in_event_mask = 0,
+ .out_state_mask = S(CLNT_ST_SVC_REQ_SENT),
+ .action = clnt_st_redirected,
+ },
+};
+
+static void clnt_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct clnt_state *st = fi->priv;
+ struct msgb *msg = data;
+ struct e1oip_echo *echo_req;
+ struct e1oip_error_ind *err_ind;
+
+ switch (event) {
+ case OCTOI_EV_RX_ECHO_REQ:
+ echo_req = msgb_l2(msg);
+ octoi_tx_echo_resp(st->peer, ntohs(echo_req->seq_nr), echo_req->data, msgb_l2len(msg));
+ break;
+ case OCTOI_EV_RX_ECHO_RESP:
+ /* FIXME: update state, peer has responded! */
+ break;
+ case OCTOI_EV_RX_ERROR_IND:
+ err_ind = msgb_l2(msg);
+ LOGPFSML(fi, LOGL_ERROR, "Rx OCTOI ERROR IND (cause=0x%08x, msg=%s)\n",
+ ntohl(err_ind->cause), err_ind->error_message);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static int clnt_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct clnt_state *st = fi->priv;
+
+ switch (fi->state) {
+ case CLNT_ST_SVC_REQ_SENT:
+ /* retransmit timer */
+ LOGPFSML(fi, LOGL_INFO, "Re-transmitting SERVICE_REQ\n");
+ octoi_tx_service_req(st->peer, st->service, st->acc->user_id,
+ PACKAGE_NAME, PACKAGE_VERSION, st->capability_flags);
+ osmo_fsm_inst_state_chg(fi, CLNT_ST_SVC_REQ_SENT, 10, 0);
+ break;
+ }
+ return 0;
+}
+
+struct osmo_fsm octoi_client_fsm = {
+ .name = "OCTOI_CLIENT",
+ .states = client_fsm_states,
+ .num_states = ARRAY_SIZE(client_fsm_states),
+ .allstate_event_mask = S(OCTOI_EV_RX_ECHO_REQ) |
+ S(OCTOI_EV_RX_ECHO_RESP) |
+ S(OCTOI_EV_RX_ERROR_IND),
+ .allstate_action = clnt_allstate_action,
+ .timer_cb = clnt_fsm_timer_cb,
+ .log_subsys = DLINP,
+ .event_names = octoi_fsm_event_names,
+};
+
+static void clnt_rx_alive_timer_cb(void *data)
+{
+ struct osmo_fsm_inst *fi = data;
+ struct clnt_state *st = fi->priv;
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+
+ if (ts.tv_sec - st->peer->last_rx_tdm > 3) {
+ LOGPFSML(fi, LOGL_NOTICE, "No TDM data received for >= 3 seconds, declaring peer dead\n");
+ osmo_fsm_inst_state_chg(fi, CLNT_ST_INIT, 0, 0);
+ osmo_fsm_inst_dispatch(fi, OCTOI_CLNT_EV_REQUEST_SERVICE, NULL);
+ } else
+ osmo_timer_schedule(&st->rx_alive_timer, 3, 0);
+}
+
+
+/* call-back function for every received OCTOI socket message for given peer */
+int octoi_clnt_fsm_rx_cb(struct octoi_peer *peer, struct msgb *msg)
+{
+ /* ensure peer->priv points to a fsm_inst */
+ if (!peer->priv) {
+ struct osmo_fsm_inst *fi;
+ struct clnt_state *st;
+
+ fi = osmo_fsm_inst_alloc(&octoi_client_fsm, peer, NULL, LOGL_DEBUG, NULL);
+ OSMO_ASSERT(fi);
+
+ st = talloc_zero(fi, struct clnt_state);
+ OSMO_ASSERT(st);
+ st->peer = peer;
+ fi->priv = st;
+
+ peer->priv = fi;
+ }
+ OSMO_ASSERT(peer->priv);
+
+ return _octoi_fsm_rx_cb(peer, msg);
+}
+
+/* start the OCTO client FSM for a specified peer */
+void octoi_clnt_start_for_peer(struct octoi_peer *peer, struct octoi_account *acc)
+{
+ OSMO_ASSERT(!peer->sock->cfg.server_mode)
+
+ /* ensure peer->priv points to a fsm_inst */
+ if (!peer->priv) {
+ struct osmo_fsm_inst *fi;
+ struct clnt_state *st;
+
+ fi = osmo_fsm_inst_alloc(&octoi_client_fsm, peer, NULL, LOGL_DEBUG, acc->user_id);
+ OSMO_ASSERT(fi);
+
+ st = talloc_zero(fi, struct clnt_state);
+ OSMO_ASSERT(st);
+ st->peer = peer;
+ st->acc = acc;
+ st->service = E1OIP_SERVICE_E1_FRAMED;
+ st->capability_flags = 0;
+ osmo_timer_setup(&st->rx_alive_timer, clnt_rx_alive_timer_cb, fi);
+ fi->priv = st;
+
+ peer->priv = fi;
+ }
+ if (!peer->iline)
+ peer->iline = e1oip_line_alloc(peer);
+
+ osmo_fsm_inst_dispatch(peer->priv, OCTOI_CLNT_EV_REQUEST_SERVICE, NULL);
+}
diff --git a/src/octoi/octoi_clnt_vty.c b/src/octoi/octoi_clnt_vty.c
new file mode 100644
index 0000000..1f8f7d1
--- /dev/null
+++ b/src/octoi/octoi_clnt_vty.c
@@ -0,0 +1,219 @@
+/*
+ * octoi_clnt_vty.c - VTY code for OCTOI client role
+ *
+ * (C) 2022 by Harald Welte <laforge@osmocom.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <talloc.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/sockaddr_str.h>
+
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/misc.h>
+
+#include "octoi.h"
+#include "octoi_sock.h"
+#include "octoi_fsm.h"
+#include "octoi_vty.h"
+
+/***********************************************************************
+ * core data structures
+ ***********************************************************************/
+
+static struct octoi_client *octoi_client_alloc(void *ctx, const char *ip, uint16_t port)
+{
+ struct octoi_client *clnt = talloc_zero(ctx, struct octoi_client);
+ int rc;
+
+ if (!clnt)
+ return NULL;
+
+ rc = osmo_sockaddr_str_from_str(&clnt->cfg.remote, ip, port);
+ if (rc < 0) {
+ talloc_free(clnt);
+ return NULL;
+ }
+
+ return clnt;
+}
+
+/* find a client for given remote IP + port */
+struct octoi_client *octoi_client_find(const char *ip, uint16_t port)
+{
+ struct octoi_client *clnt;
+
+ llist_for_each_entry(clnt, &g_octoi->clients, list) {
+ if (!strcmp(ip, clnt->cfg.remote.ip) && clnt->cfg.remote.port == port)
+ return clnt;
+ }
+ return NULL;
+}
+
+/***********************************************************************
+ * VTY
+ ***********************************************************************/
+
+static struct cmd_node clnt_node = {
+ (enum node_type) OCTOI_CLNT_NODE,
+ "%s(config-octoi-client)# ",
+ 1,
+};
+
+static struct cmd_node clnt_account_node = {
+ (enum node_type) OCTOI_CLNT_ACCOUNT_NODE,
+ "%s(config-octoi-client-account)# ",
+ 1,
+};
+
+DEFUN(cfg_client, cfg_client_cmd,
+ "octoi-client (A.B.C.D|X:X::X:X) <0-65535>",
+ "Configure an OCTOI client\n"
+ "Remote IPv4 address of OCTOI server\n"
+ "Remote IPv6 address of OCTOI server\n"
+ "Remote UDP port number of OCTOI server\n")
+{
+ const char *ip = argv[0];
+ int port = atoi(argv[1]);
+ struct octoi_client *clnt = octoi_client_find(ip, port);
+
+ if (!clnt) {
+ clnt = octoi_client_alloc(g_octoi, ip, port);
+ OSMO_ASSERT(clnt);
+ llist_add_tail(&clnt->list, &g_octoi->clients);
+ }
+
+ vty->node = OCTOI_CLNT_NODE;
+ vty->index = clnt;
+
+ return CMD_SUCCESS;
+}
+
+#if 0
+DEFUN(cfg_no_client, cfg_no_client_cmd,
+ "no octoi-client (A.B.C.D|X:X::X:X) <0-65535>",
+ NO_STR "Remove an OCTOI client\n")
+ "Remote IPv4 address of OCTOI server\n"
+ "Remote IPv6 address of OCTOI server\n"
+ "Remote UDP port number of OCTOI server\n")
+{
+}
+#endif
+
+
+DEFUN(cfg_clnt_local, cfg_clnt_local_cmd,
+ "local-bind (A.B.C.D|X:X::X:X) <0-65535>",
+ "Local OCTOI socket bind address/port\n"
+ "Local OCTOI IPv4 Address\n"
+ "Local OCTOI IPv6 Address\n"
+ "Local OCTOI UDP Port Number\n")
+{
+ struct octoi_client *clnt = vty->index;
+ int rc;
+
+ rc = osmo_sockaddr_str_from_str(&clnt->cfg.local, argv[0], atoi(argv[1]));
+ if (rc < 0) {
+ vty_out(vty, "%% sockaddr Error: %s%s", strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (clnt->sock)
+ octoi_sock_destroy(clnt->sock);
+
+ clnt->sock = octoi_sock_create_client(clnt, clnt, &clnt->cfg.local, &clnt->cfg.remote);
+ if (!clnt->sock) {
+ vty_out(vty, "%% failed to create/bind socket: %s%s", strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ clnt->sock->rx_cb = octoi_clnt_fsm_rx_cb;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_clnt_account, cfg_clnt_account_cmd,
+ "account USER_ID",
+ "Configure a local user account\n")
+{
+ struct octoi_client *clnt = vty->index;
+ const char *user_id = argv[0];
+ struct octoi_account *ac = clnt->cfg.account;
+
+ if (!ac) {
+ ac = octoi_client_account_create(clnt, user_id);
+ OSMO_ASSERT(ac);
+ } else
+ osmo_talloc_replace_string(ac, &ac->user_id, user_id);
+
+ vty->node = OCTOI_CLNT_ACCOUNT_NODE;
+ vty->index = ac;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_clnt, show_clnt_cmd,
+ "show octoi-clients",
+ SHOW_STR "Display information about the OCTOI Clients\n")
+{
+ struct octoi_client *clnt;
+
+ llist_for_each_entry(clnt, &g_octoi->clients, list) {
+ struct octoi_sock *sock = clnt->sock;
+
+ vty_show_octoi_sock(vty, sock);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int config_write_octoi_clnt(struct vty *vty)
+{
+ struct octoi_client *clnt;
+
+ llist_for_each_entry(clnt, &g_octoi->clients, list) {
+ vty_out(vty, "octoi-client %s %u%s", clnt->cfg.remote.ip, clnt->cfg.remote.port,
+ VTY_NEWLINE);
+ if (strlen(clnt->cfg.local.ip)) {
+ vty_out(vty, " local-bind %s %u%s", clnt->cfg.local.ip, clnt->cfg.local.port,
+ VTY_NEWLINE);
+ }
+ octoi_vty_write_one_account(vty, clnt->cfg.account);
+ }
+
+ return 0;
+}
+
+void octoi_client_vty_init(void)
+{
+ install_element_ve(&show_clnt_cmd);
+
+ install_node(&clnt_account_node, NULL);
+ install_element(OCTOI_CLNT_ACCOUNT_NODE, &cfg_account_ice1_serno_cmd);
+ install_element(OCTOI_CLNT_ACCOUNT_NODE, &cfg_account_ice1_line_cmd);
+ install_element(OCTOI_CLNT_ACCOUNT_NODE, &cfg_account_mode_cmd);
+
+ install_node(&clnt_node, config_write_octoi_clnt);
+ install_element(CONFIG_NODE, &cfg_client_cmd);
+ install_element(OCTOI_CLNT_NODE, &cfg_clnt_local_cmd);
+ install_element(OCTOI_CLNT_NODE, &cfg_clnt_account_cmd);
+}
diff --git a/src/octoi/octoi_fsm.c b/src/octoi/octoi_fsm.c
new file mode 100644
index 0000000..3431587
--- /dev/null
+++ b/src/octoi/octoi_fsm.c
@@ -0,0 +1,226 @@
+/*
+ * octoi_fsm.c - OCTOI Protocol / Finite State Machine interface
+ *
+ * (C) 2022 by Harald Welte <laforge@osmocom.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/msgb.h>
+
+#include <osmocom/octoi/e1oip_proto.h>
+
+#include "octoi_sock.h"
+#include "octoi_fsm.h"
+
+
+const struct value_string octoi_fsm_event_names[] = {
+ { OCTOI_EV_RX_TDM_DATA, "RX_TDM_DATA" },
+ { OCTOI_EV_RX_ECHO_REQ, "RX_ECHO_REQ" },
+ { OCTOI_EV_RX_ECHO_RESP, "RX_ECHO_RESP" },
+ { OCTOI_EV_RX_ERROR_IND, "RX_ERROR_IND" },
+
+ { OCTOI_SRV_EV_RX_SERVICE_REQ, "RX_SERVICE_REQ" },
+ { OCTOI_SRV_EV_RX_AUTH_VEC, "RX_AUTH_VEC" },
+ { OCTOI_SRV_EV_RX_AUTH_RESP, "RX_AUTH_RESP" },
+
+ { OCTOI_CLNT_EV_REQUEST_SERVICE,"REQUEST_SERVICE" },
+ { OCTOI_CLNT_EV_RX_AUTH_REQ, "RX_AUTH_REQ" },
+ { OCTOI_CLNT_EV_RX_SVC_ACK, "RX_SERVICE_ACK" },
+ { OCTOI_CLNT_EV_RX_SVC_REJ, "RX_SERVICE_REJ" },
+ { OCTOI_CLNT_EV_RX_REDIR_CMD, "RX_REDIR_CMD" },
+ { 0, NULL }
+};
+
+/* ensure given fixed-length string is zero-terminated */
+#define ENSURE_ZERO_TERM(x) ensure_zero_term(x, sizeof(x))
+static void ensure_zero_term(char *buf, size_t len)
+{
+ for (unsigned int i = 0; i < len; i++) {
+ if (buf[i] == '\0')
+ return;
+ }
+ buf[len-1] = '\0';
+}
+
+/* verify if the given OCTOI message is consistent */
+static bool octoi_msg_validate(struct osmo_fsm_inst *fi, struct msgb *msg)
+{
+ struct e1oip_msg *eip = msgb_l1(msg);
+
+ /* ensure that the minimum length is >= header length, and that the version matches */
+ if (msgb_l1len(msg) < sizeof(eip->hdr)) {
+ LOGPFSML(fi, LOGL_INFO, "Rx short message (%u < %zu)\n", msgb_l1len(msg), sizeof(eip->hdr));
+ return false;
+ }
+ if (eip->hdr.version != E1OIP_VERSION) {
+ LOGPFSML(fi, LOGL_INFO, "Rx unsupported version (%u != %u)\n", eip->hdr.version,
+ E1OIP_VERSION);
+ return false;
+ }
+
+ switch (eip->hdr.msg_type) {
+ case E1OIP_MSGT_ECHO_REQ:
+ if (msgb_l2len(msg) < sizeof(eip->u.echo))
+ goto err_msg_len;
+ break;
+ case E1OIP_MSGT_ECHO_RESP:
+ if (msgb_l2len(msg) < sizeof(eip->u.echo))
+ goto err_msg_len;
+ break;
+ case E1OIP_MSGT_TDM_DATA:
+ if (msgb_l2len(msg) < sizeof(eip->u.tdm_hdr))
+ goto err_msg_len;
+ break;
+ case E1OIP_MSGT_SERVICE_REQ:
+ if (msgb_l2len(msg) < sizeof(eip->u.service_req))
+ goto err_msg_len;
+ ENSURE_ZERO_TERM(eip->u.service_req.subscriber_id);
+ ENSURE_ZERO_TERM(eip->u.service_req.software_id);
+ ENSURE_ZERO_TERM(eip->u.service_req.software_version);
+ break;
+ case E1OIP_MSGT_SERVICE_ACK:
+ if (msgb_l2len(msg) < sizeof(eip->u.service_ack))
+ goto err_msg_len;
+ ENSURE_ZERO_TERM(eip->u.service_ack.server_id);
+ ENSURE_ZERO_TERM(eip->u.service_ack.software_id);
+ ENSURE_ZERO_TERM(eip->u.service_ack.software_version);
+ break;
+ case E1OIP_MSGT_SERVICE_REJ:
+ if (msgb_l2len(msg) < sizeof(eip->u.service_rej))
+ goto err_msg_len;
+ ENSURE_ZERO_TERM(eip->u.service_rej.reject_message);
+ break;
+ case E1OIP_MSGT_REDIR_CMD:
+ if (msgb_l2len(msg) < sizeof(eip->u.redir_cmd))
+ goto err_msg_len;
+ ENSURE_ZERO_TERM(eip->u.redir_cmd.server_ip);
+ break;
+ case E1OIP_MSGT_AUTH_REQ:
+ if (msgb_l2len(msg) < sizeof(eip->u.auth_req))
+ goto err_msg_len;
+ if (eip->u.auth_req.rand_len > sizeof(eip->u.auth_req.rand))
+ goto err_ie_len;
+ if (eip->u.auth_req.autn_len > sizeof(eip->u.auth_req.autn))
+ goto err_ie_len;
+ break;
+ case E1OIP_MSGT_AUTH_RESP:
+ if (msgb_l2len(msg) < sizeof(eip->u.auth_resp))
+ goto err_msg_len;
+ if (eip->u.auth_resp.res_len > sizeof(eip->u.auth_resp.res))
+ goto err_ie_len;
+ if (eip->u.auth_resp.auts_len > sizeof(eip->u.auth_resp.auts))
+ goto err_ie_len;
+ break;
+ case E1OIP_MSGT_ERROR_IND:
+ if (msgb_l2len(msg) < sizeof(eip->u.error_ind))
+ goto err_msg_len;
+ ENSURE_ZERO_TERM(eip->u.error_ind.error_message);
+ break;
+ default:
+ LOGPFSML(fi, LOGL_NOTICE, "Rx unknown OCTOI message type 0x%02x\n", eip->hdr.msg_type);
+ return false;
+ }
+
+ return true;
+
+err_msg_len:
+ LOGPFSML(fi, LOGL_NOTICE, "Rx truncated OCTOI message 0x%02x\n", eip->hdr.msg_type);
+ return false;
+
+err_ie_len:
+ LOGPFSML(fi, LOGL_NOTICE, "Rx invalid IE length in OCTOI message 0x%02x\n", eip->hdr.msg_type);
+ return false;
+}
+
+
+/* call-back function for every received OCTOI socket message for given peer */
+int _octoi_fsm_rx_cb(struct octoi_peer *peer, struct msgb *msg)
+{
+ struct osmo_fsm_inst *fi = peer->priv;
+ struct e1oip_hdr *e1h = msgb_l1(msg);
+
+ OSMO_ASSERT(fi);
+ OSMO_ASSERT(msgb_l1(msg));
+ OSMO_ASSERT(msgb_l2(msg));
+
+ if (!octoi_msg_validate(fi, msg))
+ return -1;
+
+ switch (e1h->msg_type) {
+ case E1OIP_MSGT_TDM_DATA:
+ osmo_fsm_inst_dispatch(fi, OCTOI_EV_RX_TDM_DATA, msg);
+ break;
+ case E1OIP_MSGT_ECHO_REQ:
+ osmo_fsm_inst_dispatch(fi, OCTOI_EV_RX_ECHO_REQ, msg);
+ break;
+ case E1OIP_MSGT_ECHO_RESP:
+ osmo_fsm_inst_dispatch(fi, OCTOI_EV_RX_ECHO_RESP, msg);
+ break;
+ case E1OIP_MSGT_ERROR_IND:
+ osmo_fsm_inst_dispatch(fi, OCTOI_EV_RX_ERROR_IND, msg);
+ break;
+
+ case E1OIP_MSGT_SERVICE_REQ:
+ osmo_fsm_inst_dispatch(fi, OCTOI_SRV_EV_RX_SERVICE_REQ, msg);
+ break;
+ case E1OIP_MSGT_AUTH_RESP:
+ osmo_fsm_inst_dispatch(fi, OCTOI_SRV_EV_RX_AUTH_RESP, msg);
+ break;
+
+ case E1OIP_MSGT_SERVICE_ACK:
+ osmo_fsm_inst_dispatch(fi, OCTOI_CLNT_EV_RX_SVC_ACK, msg);
+ break;
+ case E1OIP_MSGT_SERVICE_REJ:
+ osmo_fsm_inst_dispatch(fi, OCTOI_CLNT_EV_RX_SVC_REJ, msg);
+ break;
+ case E1OIP_MSGT_REDIR_CMD:
+ osmo_fsm_inst_dispatch(fi, OCTOI_CLNT_EV_RX_REDIR_CMD, msg);
+ break;
+ case E1OIP_MSGT_AUTH_REQ:
+ osmo_fsm_inst_dispatch(fi, OCTOI_CLNT_EV_RX_AUTH_REQ, msg);
+ break;
+
+ default:
+ LOGPFSML(fi, LOGL_NOTICE, "Rx Unknown OCTOI message type 0x%02x\n", e1h->msg_type);
+ break;
+ }
+
+ msgb_free(msg);
+ return 0;
+}
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/misc.h>
+#include "e1oip.h"
+
+void vty_show_octoi_sock(struct vty *vty, struct octoi_sock *sock)
+{
+ struct octoi_peer *peer;
+
+ vty_out(vty, "OCTOI %s Socket on "OSMO_SOCKADDR_STR_FMT"%s",
+ sock->cfg.server_mode ? "Server" : "Client",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&sock->cfg.local), VTY_NEWLINE);
+
+ llist_for_each_entry(peer, &sock->peers, list) {
+ vty_out(vty, " Peer '%s', Remote "OSMO_SOCKADDR_STR_FMT", State %s%s",
+ peer->name, OSMO_SOCKADDR_STR_FMT_ARGS(&peer->cfg.remote),
+ osmo_fsm_inst_state_name(peer->priv), VTY_NEWLINE);
+ vty_out_rate_ctr_group(vty, " ", peer->iline->ctrs);
+ vty_out_stat_item_group(vty, " ", peer->iline->stats);
+ }
+}
diff --git a/src/octoi/octoi_fsm.h b/src/octoi/octoi_fsm.h
new file mode 100644
index 0000000..b605c3a
--- /dev/null
+++ b/src/octoi/octoi_fsm.h
@@ -0,0 +1,31 @@
+#pragma once
+
+enum octoi_fsm_event {
+ /* common for server and client */
+ OCTOI_EV_RX_TDM_DATA, /* receive TDM data from client */
+ OCTOI_EV_RX_ECHO_REQ, /* receive echo request from client */
+ OCTOI_EV_RX_ECHO_RESP, /* receive echo response from client */
+ OCTOI_EV_RX_ERROR_IND, /* receive error indication */
+
+ /* only on server side */
+ OCTOI_SRV_EV_RX_SERVICE_REQ, /* receive service request from client */
+ OCTOI_SRV_EV_RX_AUTH_VEC, /* receive auth vector from HLR */
+ OCTOI_SRV_EV_RX_AUTH_RESP, /* receive auth response from client */
+
+ /* only on client side */
+ OCTOI_CLNT_EV_REQUEST_SERVICE,
+ OCTOI_CLNT_EV_RX_AUTH_REQ,
+ OCTOI_CLNT_EV_RX_SVC_ACK,
+ OCTOI_CLNT_EV_RX_SVC_REJ,
+ OCTOI_CLNT_EV_RX_REDIR_CMD,
+};
+
+#define S(x) (1 << (x))
+
+extern const struct value_string octoi_fsm_event_names[];
+
+int _octoi_fsm_rx_cb(struct octoi_peer *peer, struct msgb *msg);
+
+/* call-back function for every received OCTOI socket message for given peer */
+int octoi_srv_fsm_rx_cb(struct octoi_peer *peer, struct msgb *msg);
+int octoi_clnt_fsm_rx_cb(struct octoi_peer *peer, struct msgb *msg);
diff --git a/src/octoi/octoi_sock.c b/src/octoi/octoi_sock.c
new file mode 100644
index 0000000..943270a
--- /dev/null
+++ b/src/octoi/octoi_sock.c
@@ -0,0 +1,485 @@
+/*
+ * octoi_sock.c - OCTOI Socket handling code
+ *
+ * (C) 2022 by Harald Welte <laforge@osmocom.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdint.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+
+#include <osmocom/octoi/e1oip_proto.h>
+
+#include "octoi_sock.h"
+#include "e1oip.h"
+
+/***********************************************************************
+ * transmit to remote peer
+ ***********************************************************************/
+
+/* transmit something to an octoi peer */
+int octoi_tx(struct octoi_peer *peer, uint8_t msg_type, uint8_t flags,
+ const void *data, size_t len)
+{
+ struct e1oip_hdr hdr = {
+ .version = E1OIP_VERSION,
+ .flags = flags & 0xf,
+ .msg_type = msg_type,
+ };
+ struct iovec iov[2] = {
+ {
+ .iov_base = (void *) &hdr,
+ .iov_len = sizeof(hdr),
+ }, {
+ .iov_base = (void *) data,
+ .iov_len = len,
+ }
+ };
+ struct msghdr msgh = {
+ .msg_name = &peer->remote,
+ .msg_namelen = sizeof(peer->remote),
+ .msg_iov = iov,
+ .msg_iovlen = ARRAY_SIZE(iov),
+ .msg_control = NULL,
+ .msg_controllen = 0,
+ .msg_flags = 0,
+ };
+ int rc;
+
+ rc = sendmsg(peer->sock->ofd.fd, &msgh, 0);
+ if (rc < 0)
+ LOGPEER(peer, LOGL_ERROR, "Error in sendmsg: %s\n", strerror(errno));
+ else if (rc != (int) (sizeof(hdr) + len))
+ LOGPEER(peer, LOGL_ERROR, "Short write in sendmsg: %d != %zu\n", rc, sizeof(hdr)+len);
+
+ return rc;
+}
+
+static int _octoi_tx_echo(struct octoi_peer *peer, bool is_req, uint16_t seq_nr,
+ const uint8_t *data, size_t data_len)
+{
+ enum e1oip_msgtype msgt;
+ struct {
+ struct e1oip_echo echo;
+ uint8_t buf[data_len];
+ } u;
+
+ u.echo.seq_nr = htons(seq_nr);
+ memcpy(u.echo.data, data, data_len);
+
+ if (is_req)
+ msgt = E1OIP_MSGT_ECHO_REQ;
+ else
+ msgt = E1OIP_MSGT_ECHO_RESP;
+
+ return octoi_tx(peer, msgt, 0, &u, sizeof(u));
+}
+
+int octoi_tx_echo_req(struct octoi_peer *peer, uint16_t seq_nr, const uint8_t *data, size_t data_len)
+{
+ LOGPEER(peer, LOGL_DEBUG, "Tx ECHO_REQ\n");
+ return _octoi_tx_echo(peer, true, seq_nr, data, data_len);
+}
+
+int octoi_tx_echo_resp(struct octoi_peer *peer, uint16_t seq_nr, const uint8_t *data, size_t data_len)
+{
+ LOGPEER(peer, LOGL_DEBUG, "Tx ECHO_RESP\n");
+ return _octoi_tx_echo(peer, false, seq_nr, data, data_len);
+}
+
+int octoi_tx_service_req(struct octoi_peer *peer, uint32_t service, const char *subscr_id,
+ const char *software_id, const char *software_version,
+ uint32_t capability_flags)
+{
+ struct e1oip_service_req service_req;
+
+ memset(&service_req, 0, sizeof(service_req));
+ service_req.requested_service = htonl(service);
+ OSMO_STRLCPY_ARRAY(service_req.subscriber_id, subscr_id);
+ OSMO_STRLCPY_ARRAY(service_req.software_id, software_id);
+ OSMO_STRLCPY_ARRAY(service_req.software_version, software_version);
+ service_req.capability_flags = htonl(capability_flags);
+
+ LOGPEER(peer, LOGL_INFO, "Tx SERVICE_REQ\n");
+ return octoi_tx(peer, E1OIP_MSGT_SERVICE_REQ, 0, &service_req, sizeof(service_req));
+}
+
+int octoi_tx_redir_cmd(struct octoi_peer *peer, const char *server_ip, uint16_t server_port)
+{
+ struct e1oip_redir_cmd redir;
+
+ memset(&redir, 0, sizeof(redir));
+ OSMO_STRLCPY_ARRAY(redir.server_ip, server_ip);
+ redir.server_port = htons(server_port);
+
+ LOGPEER(peer, LOGL_INFO, "Tx REDIR_CMD\n");
+ return octoi_tx(peer, E1OIP_MSGT_REDIR_CMD, 0, &redir, sizeof(redir));
+}
+
+int octoi_tx_auth_req(struct octoi_peer *peer, uint8_t rand_len, const uint8_t *rand,
+ uint8_t autn_len, const uint8_t *autn)
+{
+ struct e1oip_auth_req areq;
+
+ memset(&areq, 0, sizeof(areq));
+
+ OSMO_ASSERT(rand_len <= sizeof(areq.rand));
+ OSMO_ASSERT(autn_len <= sizeof(areq.autn));
+
+ areq.rand_len = rand_len;
+ memcpy(areq.rand, rand, rand_len);
+ areq.autn_len = autn_len;
+ memcpy(areq.autn, autn, autn_len);
+
+ LOGPEER(peer, LOGL_INFO, "Tx AUTH_REQ\n");
+ return octoi_tx(peer, E1OIP_MSGT_AUTH_REQ, 0, &areq, sizeof(areq));
+}
+
+
+int octoi_tx_auth_resp(struct octoi_peer *peer, uint8_t res_len, const uint8_t *res,
+ uint8_t auts_len, const uint8_t *auts)
+{
+ struct e1oip_auth_resp aresp;
+
+ memset(&aresp, 0, sizeof(aresp));
+
+ OSMO_ASSERT(res_len <= sizeof(aresp.res));
+ OSMO_ASSERT(auts_len <= sizeof(aresp.auts));
+
+ aresp.res_len = res_len;
+ memcpy(aresp.res, res, res_len);
+ aresp.auts_len = auts_len;
+ memcpy(aresp.auts, auts, auts_len);
+
+ LOGPEER(peer, LOGL_INFO, "Tx AUTH_RESP\n");
+ return octoi_tx(peer, E1OIP_MSGT_AUTH_RESP, 0, &aresp, sizeof(aresp));
+}
+
+int octoi_tx_service_ack(struct octoi_peer *peer, uint32_t assigned_service,
+ const char *server_id, const char *software_id,
+ const char *software_version, uint32_t capability_flags)
+{
+ struct e1oip_service_ack service_ack;
+
+ memset(&service_ack, 0, sizeof(service_ack));
+ service_ack.assigned_service = htonl(assigned_service);
+ OSMO_STRLCPY_ARRAY(service_ack.server_id, server_id);
+ OSMO_STRLCPY_ARRAY(service_ack.software_id, software_id);
+ OSMO_STRLCPY_ARRAY(service_ack.software_version, software_version);
+ service_ack.capability_flags = htonl(capability_flags);
+
+ LOGPEER(peer, LOGL_INFO, "Tx SERVICE_ACK\n");
+ return octoi_tx(peer, E1OIP_MSGT_SERVICE_ACK, 0, &service_ack, sizeof(service_ack));
+}
+
+int octoi_tx_service_rej(struct octoi_peer *peer, uint32_t rejected_service, const char *message)
+{
+ struct e1oip_service_rej service_rej;
+
+ memset(&service_rej, 0, sizeof(service_rej));
+ service_rej.rejected_service = htonl(rejected_service);
+ OSMO_STRLCPY_ARRAY(service_rej.reject_message, message);
+
+ LOGPEER(peer, LOGL_INFO, "Tx SERVICE_REJ\n");
+ return octoi_tx(peer, E1OIP_MSGT_SERVICE_REJ, 0, &service_rej, sizeof(service_rej));
+}
+
+int octoi_tx_error_ind(struct octoi_peer *peer, uint32_t cause, const char *message,
+ const uint8_t *orig, size_t orig_len)
+{
+ struct {
+ struct e1oip_error_ind error_ind;
+ uint8_t orig[orig_len];
+ } u;
+
+ u.error_ind.cause = htonl(cause);
+ OSMO_STRLCPY_ARRAY(u.error_ind.error_message, message);
+ memcpy(&u.orig, orig, orig_len);
+
+ LOGPEER(peer, LOGL_INFO, "Tx ERROR_IND\n");
+ return octoi_tx(peer, E1OIP_MSGT_ERROR_IND, 0, &u, sizeof(u));
+}
+
+
+/***********************************************************************
+ * socket
+ ***********************************************************************/
+
+static int sockaddr_cmp(const struct sockaddr *x, const struct sockaddr *y)
+{
+ if (x->sa_family != y->sa_family)
+ return -1;
+
+ if (x->sa_family == AF_UNIX) {
+ const struct sockaddr_un *xun = (void *)x, *yun = (void *)y;
+ int r = strcmp(xun->sun_path, yun->sun_path);
+ if (r != 0)
+ return r;
+ } else if (x->sa_family == AF_INET) {
+ const struct sockaddr_in *xin = (void *)x, *yin = (void *)y;
+ if (xin->sin_addr.s_addr != yin->sin_addr.s_addr)
+ return -1;
+ if (xin->sin_port != yin->sin_port)
+ return -1;
+ } else if (x->sa_family == AF_INET6) {
+ const struct sockaddr_in6 *xin6 = (void *)x, *yin6 = (void *)y;
+ int r = memcmp(xin6->sin6_addr.s6_addr, yin6->sin6_addr.s6_addr, sizeof(xin6->sin6_addr.s6_addr));
+ if (r != 0)
+ return r;
+ if (xin6->sin6_port != yin6->sin6_port)
+ return -1;
+ if (xin6->sin6_flowinfo != yin6->sin6_flowinfo)
+ return -1;
+ if (xin6->sin6_scope_id != yin6->sin6_scope_id)
+ return -1;
+ } else {
+ OSMO_ASSERT(0);
+ }
+ return 0;
+}
+
+static struct octoi_peer *find_peer_by_sockaddr(struct octoi_sock *sock, const struct sockaddr *sa)
+{
+ struct octoi_peer *peer;
+
+ llist_for_each_entry(peer, &sock->peers, list) {
+ if (!sockaddr_cmp(sa, (struct sockaddr *) &peer->remote))
+ return peer;
+ }
+ return NULL;
+}
+
+static struct octoi_peer *
+alloc_peer(struct octoi_sock *sock, const struct sockaddr *sa, socklen_t sa_len)
+{
+ struct octoi_peer *peer = talloc_zero(sock, struct octoi_peer);
+
+ if (!peer)
+ return NULL;
+
+ OSMO_ASSERT(sa_len <= sizeof(peer->remote));
+ memcpy(&peer->remote, sa, sa_len);
+
+ peer->sock = sock;
+ llist_add_tail(&peer->list, &sock->peers);
+
+ return peer;
+}
+
+static int octoi_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct octoi_sock *sock = ofd->data;
+ struct msgb *msg;
+ struct sockaddr_storage ss_remote;
+ socklen_t ss_remote_len = sizeof(ss_remote);
+ struct octoi_peer *peer;
+ int rc;
+
+ if (what & OSMO_FD_WRITE) {
+ LOGP(DLINP, LOGL_INFO, "non-blocking connect succeeded\n");
+ osmo_fd_write_disable(ofd);
+ }
+
+
+ if (what & OSMO_FD_READ) {
+ msg = msgb_alloc_c(sock, 2048, "OCTOI Rx");
+ OSMO_ASSERT(msg);
+ rc = recvfrom(ofd->fd, msgb_data(msg), msgb_tailroom(msg), 0,
+ (struct sockaddr *) &ss_remote, &ss_remote_len);
+ if (rc <= 0) {
+ msgb_free(msg);
+ return -1;
+ }
+ msgb_put(msg, rc);
+ msg->l1h = msg->data;
+ if (msgb_l1len(msg) < sizeof(struct e1oip_hdr)) {
+ msgb_free(msg);
+ return -2;
+ }
+ msg->l2h = msg->l1h + sizeof(struct e1oip_hdr);
+
+ /* look-up octoi_peer based on remote address */
+ peer = find_peer_by_sockaddr(sock, (struct sockaddr *) &ss_remote);
+ if (!peer) {
+ peer = alloc_peer(sock, (struct sockaddr *) &ss_remote, ss_remote_len);
+ if (peer) {
+ osmo_sockaddr_str_from_sockaddr(&peer->cfg.remote, &ss_remote);
+ osmo_talloc_replace_string_fmt(peer, &peer->name, OSMO_SOCKADDR_STR_FMT,
+ OSMO_SOCKADDR_STR_FMT_ARGS(&peer->cfg.remote));
+ LOGPEER(peer, LOGL_INFO, "peer created\n");
+ }
+ }
+ OSMO_ASSERT(peer);
+
+ /* dispatch received message to peer */
+ rc = sock->rx_cb(peer, msg);
+ if (rc < 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+void octoi_peer_destroy(struct octoi_peer *peer)
+{
+ if (!peer)
+ return;
+
+ peer->tdm_permitted = false;
+ peer->sock = NULL;
+ e1oip_line_destroy(peer->iline);
+
+ llist_del(&peer->list);
+ talloc_free(peer);
+}
+
+static struct octoi_sock *octoi_sock_create(void *ctx)
+{
+ struct octoi_sock *sock = talloc_zero(ctx, struct octoi_sock);
+
+ if (!sock)
+ return NULL;
+
+ INIT_LLIST_HEAD(&sock->peers);
+ osmo_fd_setup(&sock->ofd, -1, OSMO_FD_READ, octoi_fd_cb, sock, 0);
+
+ return sock;
+}
+
+struct octoi_sock *octoi_sock_create_server(void *ctx, void *priv, const struct osmo_sockaddr_str *local)
+{
+ struct octoi_sock *sock = octoi_sock_create(ctx);
+ struct sockaddr_storage sa_local;
+ int rc;
+
+ OSMO_ASSERT(sock);
+
+ sock->priv = priv;
+ sock->cfg.server_mode = true;
+ sock->cfg.local = *local;
+
+ /* bind to local addr/port; don't connect to any remote as we have many */
+ osmo_sockaddr_str_to_sockaddr(&sock->cfg.local, &sa_local);
+ rc = osmo_sock_init_osa_ofd(&sock->ofd, SOCK_DGRAM, IPPROTO_UDP,
+ (struct osmo_sockaddr *) &sa_local, NULL,
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_NONBLOCK);
+
+ if (rc < 0) {
+ LOGP(DLINP, LOGL_ERROR, "Unable to create OCTOI server socket\n");
+ talloc_free(sock);
+ return NULL;
+ }
+
+ LOGP(DLINP, LOGL_NOTICE, "OCTOI server socket at "OSMO_SOCKADDR_STR_FMT"\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(local));
+
+ return sock;
+}
+
+struct octoi_sock *octoi_sock_create_client(void *ctx, void *priv, const struct osmo_sockaddr_str *local,
+ const struct osmo_sockaddr_str *remote)
+{
+ struct octoi_sock *sock = octoi_sock_create(ctx);
+ struct sockaddr_storage sa_remote;
+ struct octoi_peer *peer;
+ int rc;
+
+ OSMO_ASSERT(sock);
+
+ sock->priv = priv;
+ sock->cfg.server_mode = false;
+ if (local)
+ sock->cfg.local = *local;
+
+ /* bind to local addr/port; don't connect to any remote as we have many */
+ osmo_sockaddr_str_to_sockaddr(remote, &sa_remote);
+ if (local) {
+ struct sockaddr_storage sa_local;
+ osmo_sockaddr_str_to_sockaddr(&sock->cfg.local, &sa_local);
+ rc = osmo_sock_init_osa_ofd(&sock->ofd, SOCK_DGRAM, IPPROTO_UDP,
+ (struct osmo_sockaddr *) &sa_local,
+ (struct osmo_sockaddr *) &sa_remote,
+ OSMO_SOCK_F_CONNECT | OSMO_SOCK_F_BIND | OSMO_SOCK_F_NONBLOCK);
+ } else {
+ rc = osmo_sock_init_osa_ofd(&sock->ofd, SOCK_DGRAM, IPPROTO_UDP,
+ NULL, (struct osmo_sockaddr *) &sa_remote,
+ OSMO_SOCK_F_CONNECT | OSMO_SOCK_F_NONBLOCK);
+ }
+ if (rc < 0) {
+ LOGP(DLINP, LOGL_ERROR, "Unable to create OCTOI client socket\n");
+ talloc_free(sock);
+ return NULL;
+ }
+
+ LOGP(DLINP, LOGL_NOTICE, "OCTOI client socket to "OSMO_SOCKADDR_STR_FMT"\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(remote));
+
+ /* create [the only] peer */
+ peer = alloc_peer(sock, (struct sockaddr *) &sa_remote, sizeof(sa_remote));
+ peer->cfg.remote = *remote;
+ osmo_talloc_replace_string_fmt(peer, &peer->name, OSMO_SOCKADDR_STR_FMT,
+ OSMO_SOCKADDR_STR_FMT_ARGS(remote));
+
+ return sock;
+}
+
+void octoi_sock_destroy(struct octoi_sock *sock)
+{
+ struct octoi_peer *p1, *p2;
+
+ if (!sock)
+ return;
+
+ llist_for_each_entry_safe(p1, p2, &sock->peers, list) {
+ OSMO_ASSERT(p1->sock == sock);
+ p1->sock = NULL;
+ /* FIXME: destroy FSM / priv */
+ llist_del(&p1->list);
+ talloc_free(p1);
+ }
+
+ osmo_fd_unregister(&sock->ofd);
+ close(sock->ofd.fd);
+
+ LOGP(DLINP, LOGL_NOTICE, "OCTOI %s socket destroyed\n",
+ sock->cfg.server_mode ? "server" : "client");
+
+ talloc_free(sock);
+}
+
+/* return the (only) peer of a octoi_sock client */
+struct octoi_peer *octoi_sock_client_get_peer(struct octoi_sock *sock)
+{
+ if (!sock)
+ return NULL;
+ OSMO_ASSERT(!sock->cfg.server_mode);
+ OSMO_ASSERT(llist_count(&sock->peers) == 1);
+ return llist_entry(sock->peers.next, struct octoi_peer, list);
+}
diff --git a/src/octoi/octoi_sock.h b/src/octoi/octoi_sock.h
new file mode 100644
index 0000000..cf7b74b
--- /dev/null
+++ b/src/octoi/octoi_sock.h
@@ -0,0 +1,97 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <sys/socket.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+
+/* Server side:
+ * - one socket, bound to configured port but not connected
+ * - multiple peers
+ *
+ * Client side:
+ * - one socket, [optionally locally bound] + connected to configured remote IP+port
+ * - single peer
+ */
+
+#define LOGPEER(peer, lvl, fmt, args ...) \
+ LOGP(DLINP, lvl, "%s: " fmt, (peer)->name, ## args)
+
+struct e1oip_line;
+struct octoi_peer;
+
+struct octoi_sock {
+ struct llist_head list; /* member in global list */
+ struct osmo_fd ofd; /* file descriptor */
+ struct llist_head peers; /* list of peers */
+ void *priv;
+
+ int (*rx_cb)(struct octoi_peer *peer, struct msgb *msg);
+
+ struct {
+ bool server_mode;
+ struct osmo_sockaddr_str local; /* local address */
+ } cfg;
+};
+
+struct octoi_peer {
+ struct llist_head list; /* member in octoi_sock.peers */
+ struct octoi_sock *sock; /* back-pointer to sock */
+ struct sockaddr_storage remote; /* remote socket address */
+ time_t last_rx_tdm; /* last time we received TDM from peer */
+ struct e1oip_line *iline;
+ bool tdm_permitted; /* TDM messages are permitted (now) */
+ char *name; /* human-readable name (just for logging) */
+ void *priv; /* private data, e.g. fsm instance */
+
+ struct {
+ struct osmo_sockaddr_str remote; /* remote address */
+ } cfg;
+};
+
+struct octoi_sock *octoi_sock_create_server(void *ctx, void *priv,
+ const struct osmo_sockaddr_str *local);
+
+struct octoi_sock *octoi_sock_create_client(void *ctx, void *priv,
+ const struct osmo_sockaddr_str *local,
+ const struct osmo_sockaddr_str *remote);
+
+void octoi_sock_destroy(struct octoi_sock *sock);
+
+struct octoi_peer *octoi_sock_client_get_peer(struct octoi_sock *sock);
+
+void octoi_peer_destroy(struct octoi_peer *peer);
+
+int octoi_tx(struct octoi_peer *peer, uint8_t msg_type, uint8_t flags,
+ const void *data, size_t len);
+
+int octoi_tx_echo_req(struct octoi_peer *peer, uint16_t seq_nr, const uint8_t *data, size_t data_len);
+
+int octoi_tx_echo_resp(struct octoi_peer *peer, uint16_t seq_nr, const uint8_t *data, size_t data_len);
+
+int octoi_tx_service_req(struct octoi_peer *peer, uint32_t service, const char *subscr_id,
+ const char *software_id, const char *software_version,
+ uint32_t capability_flags);
+
+int octoi_tx_redir_cmd(struct octoi_peer *peer, const char *server_ip, uint16_t server_port);
+
+int octoi_tx_auth_req(struct octoi_peer *peer, uint8_t rand_len, const uint8_t *rand,
+ uint8_t autn_len, const uint8_t *autn);
+
+int octoi_tx_auth_resp(struct octoi_peer *peer, uint8_t res_len, const uint8_t *res,
+ uint8_t auts_len, const uint8_t *auts);
+
+int octoi_tx_service_ack(struct octoi_peer *peer, uint32_t assigned_service,
+ const char *server_id, const char *software_id,
+ const char *software_version, uint32_t capability_flags);
+
+int octoi_tx_service_rej(struct octoi_peer *peer, uint32_t rejected_service, const char *message);
+
+int octoi_tx_error_ind(struct octoi_peer *peer, uint32_t cause, const char *message,
+ const uint8_t *orig, size_t orig_len);
diff --git a/src/octoi/octoi_srv_fsm.c b/src/octoi/octoi_srv_fsm.c
new file mode 100644
index 0000000..07dc1a7
--- /dev/null
+++ b/src/octoi/octoi_srv_fsm.c
@@ -0,0 +1,379 @@
+/*
+ * octoi_srv_fsm.c - OCTOI Server-side Finite State Machine
+ *
+ * (C) 2022 by Harald Welte <laforge@osmocom.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/timer.h>
+
+#include <osmocom/octoi/octoi.h>
+#include <osmocom/octoi/e1oip_proto.h>
+
+#include "octoi.h"
+#include "octoi_sock.h"
+#include "octoi_fsm.h"
+#include "e1oip.h"
+
+#define SUPPORTED_CAPABILITIES 0x00000000
+
+enum octoi_server_fsm_state {
+ SRV_ST_INIT, /* just created [for new client] */
+ SRV_ST_WAIT_AUTH_VEC, /* service request from client */
+ SRV_ST_WAIT_AUTH_RESP, /* auth req sent, wait for auth resp */
+ SRV_ST_ACCEPTED, /* service accepted */
+ SRV_ST_REJECTED, /* service rejected */
+ SRV_ST_REDIRECTED, /* service redirected */
+};
+
+struct srv_state {
+ struct octoi_peer *peer; /* peer to which we belong */
+ uint32_t service; /* service we are providing */
+ uint32_t capability_flags; /* negotiated capabilities */
+ struct {
+ char *subscriber_id;
+ char *software_id;
+ char *software_version;
+ uint32_t capability_flags;
+ } remote;
+ struct osmo_timer_list rx_alive_timer;
+ struct octoi_account *acc;
+ void *app_priv; /* application private data */
+ const char *rej_str;
+};
+
+static void srv_st_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct srv_state *st = fi->priv;
+ struct octoi_server *srv = st->peer->priv;
+ struct octoi_account *acc;
+ struct msgb *msg = data;
+ struct e1oip_service_req *srv_req = NULL;
+ uint32_t service;
+
+ switch (event) {
+ case OCTOI_SRV_EV_RX_SERVICE_REQ:
+ srv_req = (struct e1oip_service_req *) msgb_l2(msg);
+ service = ntohl(srv_req->requested_service);
+ LOGPFSML(fi, LOGL_INFO, "Rx SERVICE REQ (service=%u, subscriber='%s', "
+ "software='%s'/'%s', capabilities=0x%08x)\n", service,
+ srv_req->subscriber_id, srv_req->software_id, srv_req->software_version,
+ htonl(srv_req->capability_flags));
+ if (service != E1OIP_SERVICE_E1_FRAMED) {
+ osmo_fsm_inst_state_chg(fi, SRV_ST_REJECTED, 0, 0);
+ octoi_tx_service_rej(st->peer, service, "Unsupported service");
+ break;
+ }
+ /* fill peer structure with parameters received */
+ st->service = service;
+ osmo_fsm_inst_update_id(fi, srv_req->subscriber_id);
+ osmo_talloc_replace_string(st->peer, &st->remote.subscriber_id, srv_req->subscriber_id);
+ osmo_talloc_replace_string(st->peer, &st->remote.software_id, srv_req->software_id);
+ osmo_talloc_replace_string(st->peer, &st->remote.software_version, srv_req->software_version);
+ st->remote.capability_flags = ntohl(srv_req->capability_flags);
+ /* intersect capabilities */
+ st->capability_flags = st->remote.capability_flags & SUPPORTED_CAPABILITIES;
+
+ /* TODO: later we would want to start looking up the subscriber in the HLR
+ * and request authentication tuples. */
+
+ /* check subscriber */
+ acc = octoi_account_find(st->peer->sock->priv, st->remote.subscriber_id);
+ if (!acc) {
+ LOGPFSML(fi, LOGL_NOTICE, "Could not find user account %s, rejecting\n",
+ st->remote.subscriber_id);
+ st->rej_str = "Unknown user";
+ goto reject;
+ }
+ st->acc = acc;
+
+ switch (acc->mode) {
+ case ACCOUNT_MODE_ICE1USB:
+ case ACCOUNT_MODE_DAHDI:
+ /* check if a matching device exists for that account */
+ st->app_priv = g_octoi->ops->client_connected(srv, st->peer, acc);
+ if (!st->app_priv) {
+ LOGPFSML(fi, LOGL_NOTICE, "Could not find E1 line for account %s, "
+ "rejecting\n", acc->user_id);
+ st->rej_str = "No line for user";
+ goto reject;
+ }
+ osmo_talloc_replace_string(st->peer, &st->peer->name, acc->user_id);
+ osmo_fsm_inst_state_chg(fi, SRV_ST_ACCEPTED, 0, 0);
+ octoi_tx_service_ack(st->peer, st->service, "TODO-SRV", PACKAGE_NAME,
+ PACKAGE_VERSION, st->capability_flags);
+ break;
+ case ACCOUNT_MODE_REDIRECT:
+ octoi_tx_redir_cmd(st->peer, acc->u.redirect.to.ip, acc->u.redirect.to.port);
+ osmo_fsm_inst_state_chg(fi, SRV_ST_REDIRECTED, 10, 0);
+ break;
+ case ACCOUNT_MODE_NONE:
+ LOGPFSML(fi, LOGL_NOTICE, "User account %s has mode 'none', rejecting\n",
+ acc->user_id);
+ default:
+ st->rej_str = "Unsupported mode for user";
+ goto reject;
+ break;
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ return;
+
+reject:
+ octoi_tx_service_rej(st->peer, st->service, st->rej_str);
+ osmo_fsm_inst_state_chg(fi, SRV_ST_REJECTED, 10, 0);
+}
+
+static void srv_st_wait_auth_vec(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case OCTOI_SRV_EV_RX_AUTH_VEC:
+ /* TODO */
+ //octoi_tx_auth_req(peer,
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void srv_st_wait_auth_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case OCTOI_SRV_EV_RX_AUTH_RESP:
+ /* TODO */
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void srv_st_accepted_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct srv_state *st = fi->priv;
+
+ st->peer->tdm_permitted = true;
+ osmo_timer_schedule(&st->rx_alive_timer, 3, 0);
+}
+
+static void srv_st_accepted(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct srv_state *st = fi->priv;
+
+ switch (event) {
+ case OCTOI_SRV_EV_RX_AUTH_RESP: /* Rx re-transmission from client side */
+ /* re-transmit ack */
+ octoi_tx_service_ack(st->peer, st->service, "TODO-SRV", PACKAGE_NAME,
+ PACKAGE_VERSION, st->capability_flags);
+ osmo_timer_schedule(&st->rx_alive_timer, 3, 0);
+ break;
+ case OCTOI_EV_RX_TDM_DATA:
+ e1oip_rcvmsg_tdm_data(st->peer->iline, data);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void srv_st_accepted_onleave(struct osmo_fsm_inst *fi, uint32_t next_state)
+{
+ struct srv_state *st = fi->priv;
+
+ osmo_timer_del(&st->rx_alive_timer);
+ st->peer->tdm_permitted = false;
+}
+
+static void srv_st_rejected(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct srv_state *st = fi->priv;
+
+ switch (event) {
+ case OCTOI_SRV_EV_RX_SERVICE_REQ:
+ case OCTOI_SRV_EV_RX_AUTH_RESP: /* Rx re-transmission from client side */
+ octoi_tx_service_rej(st->peer, st->service, st->rej_str);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void srv_st_redirected(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct srv_state *st = fi->priv;
+
+ switch (event) {
+ case OCTOI_SRV_EV_RX_SERVICE_REQ:
+ case OCTOI_SRV_EV_RX_AUTH_RESP: /* Rx re-transmission from client side */
+ octoi_tx_redir_cmd(st->peer, st->acc->u.redirect.to.ip, st->acc->u.redirect.to.port);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static const struct osmo_fsm_state server_fsm_states[] = {
+ [SRV_ST_INIT] = {
+ .name = "INIT",
+ .in_event_mask = S(OCTOI_SRV_EV_RX_SERVICE_REQ), /* retransmit */
+ .out_state_mask = S(SRV_ST_WAIT_AUTH_VEC) | S(SRV_ST_ACCEPTED) | S(SRV_ST_REJECTED),
+ .action = srv_st_init,
+ },
+ [SRV_ST_WAIT_AUTH_VEC] = {
+ .name = "WAIT_AUTH_VEC",
+ .in_event_mask = S(OCTOI_SRV_EV_RX_AUTH_VEC),
+ .out_state_mask = S(SRV_ST_WAIT_AUTH_RESP) | S(SRV_ST_REJECTED),
+ .action = srv_st_wait_auth_vec,
+ },
+ [SRV_ST_WAIT_AUTH_RESP] = {
+ .name = "WAIT_AUTH_RESP",
+ .in_event_mask = S(OCTOI_SRV_EV_RX_AUTH_RESP),
+ .out_state_mask = S(SRV_ST_ACCEPTED) | S(SRV_ST_REJECTED) | S(SRV_ST_REDIRECTED),
+ .action = srv_st_wait_auth_resp,
+ },
+ [SRV_ST_ACCEPTED] = {
+ .name = "ACCEPTED",
+ .in_event_mask = S(OCTOI_SRV_EV_RX_AUTH_RESP) | /* retransmit */
+ S(OCTOI_EV_RX_TDM_DATA),
+ .action = srv_st_accepted,
+ .onenter = srv_st_accepted_onenter,
+ .onleave = srv_st_accepted_onleave,
+ },
+ [SRV_ST_REJECTED] = {
+ .name = "REJECTED",
+ .in_event_mask = S(OCTOI_SRV_EV_RX_SERVICE_REQ) | /* retransmit */
+ S(OCTOI_SRV_EV_RX_AUTH_RESP), /* retransmit */
+ .action = srv_st_rejected,
+ },
+ [SRV_ST_REDIRECTED] = {
+ .name = "REDIRECTED",
+ .in_event_mask = S(OCTOI_SRV_EV_RX_SERVICE_REQ) | /* retransmit */
+ S(OCTOI_SRV_EV_RX_AUTH_RESP), /* retransmit */
+ .action = srv_st_redirected,
+ },
+};
+
+static void srv_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct srv_state *st = fi->priv;
+ struct msgb *msg = data;
+ struct e1oip_echo *echo_req;
+ struct e1oip_error_ind *err_ind;
+
+ switch (event) {
+ case OCTOI_EV_RX_ECHO_REQ:
+ echo_req = msgb_l2(msg);
+ octoi_tx_echo_resp(st->peer, ntohs(echo_req->seq_nr), echo_req->data, msgb_l2len(msg));
+ break;
+ case OCTOI_EV_RX_ECHO_RESP:
+ /* TODO: update state, peer has responded! */
+ break;
+ case OCTOI_EV_RX_ERROR_IND:
+ err_ind = msgb_l2(msg);
+ LOGPFSML(fi, LOGL_ERROR, "Rx OCTOI ERROR IND (cause=0x%08x, msg=%s)\n",
+ ntohl(err_ind->cause), err_ind->error_message);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static int srv_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ switch (fi->state) {
+ case SRV_ST_REJECTED:
+ case SRV_ST_REDIRECTED:
+ /* 10s timeout has expired, we can now forget about this peer */
+ /* request termination */
+ return 1;
+ }
+
+ return 0;
+}
+
+static void srv_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct srv_state *st = fi->priv;
+
+ osmo_timer_del(&st->rx_alive_timer);
+
+ /* as long as 'fi' lives within 'peer' we cannot recursively destroy peer */
+ talloc_steal(OTC_SELECT, fi);
+
+ if (g_octoi->ops->peer_disconnected)
+ g_octoi->ops->peer_disconnected(st->peer);
+
+ octoi_peer_destroy(st->peer);
+}
+
+struct osmo_fsm octoi_server_fsm = {
+ .name = "OCTOI_SERVER",
+ .states = server_fsm_states,
+ .num_states = ARRAY_SIZE(server_fsm_states),
+ .allstate_event_mask = S(OCTOI_EV_RX_ECHO_REQ) |
+ S(OCTOI_EV_RX_ECHO_RESP) |
+ S(OCTOI_EV_RX_ERROR_IND),
+ .allstate_action = srv_allstate_action,
+ .timer_cb = srv_fsm_timer_cb,
+ .log_subsys = DLINP,
+ .event_names = octoi_fsm_event_names,
+ .cleanup = srv_fsm_cleanup,
+};
+
+
+static void srv_rx_alive_timer_cb(void *data)
+{
+ struct osmo_fsm_inst *fi = data;
+ struct srv_state *st = fi->priv;
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+
+ if (ts.tv_sec - st->peer->last_rx_tdm > 3) {
+ LOGPFSML(fi, LOGL_NOTICE, "No TDM data received for >= 3 seconds, declaring peer dead\n");
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_TIMEOUT, NULL);
+ } else
+ osmo_timer_schedule(&st->rx_alive_timer, 3, 0);
+}
+
+/* call-back function for every received OCTOI socket message for given peer */
+int octoi_srv_fsm_rx_cb(struct octoi_peer *peer, struct msgb *msg)
+{
+
+ /* ensure peer->priv points to a fsm_inst */
+ if (!peer->priv) {
+ struct osmo_fsm_inst *fi;
+ struct srv_state *st;
+
+ fi = osmo_fsm_inst_alloc(&octoi_server_fsm, peer, NULL, LOGL_DEBUG, NULL);
+ OSMO_ASSERT(fi);
+
+ st = talloc_zero(fi, struct srv_state);
+ OSMO_ASSERT(st);
+ st->peer = peer;
+ osmo_timer_setup(&st->rx_alive_timer, srv_rx_alive_timer_cb, fi);
+ fi->priv = st;
+
+ peer->priv = fi;
+ }
+ OSMO_ASSERT(peer->priv);
+ if (!peer->iline)
+ peer->iline = e1oip_line_alloc(peer);
+
+ return _octoi_fsm_rx_cb(peer, msg);
+}
diff --git a/src/octoi/octoi_srv_vty.c b/src/octoi/octoi_srv_vty.c
new file mode 100644
index 0000000..e5b91c7
--- /dev/null
+++ b/src/octoi/octoi_srv_vty.c
@@ -0,0 +1,386 @@
+/*
+ * octoi_srv_vty.c - VTY interface for OCTOI server side
+ *
+ * (C) 2022 by Harald Welte <laforge@osmocom.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <talloc.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/core/fsm.h>
+
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/misc.h>
+
+#include "octoi.h"
+#include "octoi_sock.h"
+#include "octoi_fsm.h"
+#include "octoi_vty.h"
+
+/***********************************************************************
+ * core data structures
+ ***********************************************************************/
+
+const struct value_string octoi_account_mode_name[] = {
+ { ACCOUNT_MODE_NONE, "none" },
+ { ACCOUNT_MODE_ICE1USB, "ice1usb" },
+ { ACCOUNT_MODE_REDIRECT, "redirect" },
+ { ACCOUNT_MODE_DAHDI, "dahdi" },
+ { 0, NULL }
+};
+
+static struct octoi_account *_account_create(void *ctx, const char *user_id)
+{
+ struct octoi_account *ac = talloc_zero(ctx, struct octoi_account);
+ if (!ac)
+ return NULL;
+
+ ac->user_id = talloc_strdup(ac, user_id);
+ if (!ac->user_id) {
+ talloc_free(ac);
+ return NULL;
+ }
+
+ return ac;
+}
+
+static struct octoi_account *octoi_server_account_create(struct octoi_server *srv, const char *user_id)
+{
+ struct octoi_account *ac = _account_create(srv, user_id);
+ if (!ac)
+ return NULL;
+
+ llist_add_tail(&ac->list, &srv->cfg.accounts);
+
+ return ac;
+}
+
+struct octoi_account *octoi_client_account_create(struct octoi_client *clnt, const char *user_id)
+{
+
+ struct octoi_account *ac = _account_create(clnt, user_id);
+ if (!ac)
+ return NULL;
+
+ OSMO_ASSERT(!clnt->cfg.account);
+ clnt->cfg.account = ac;
+
+ ac->mode = ACCOUNT_MODE_ICE1USB;
+
+ return ac;
+}
+
+struct octoi_account *octoi_account_find(struct octoi_server *srv, const char *user_id)
+{
+ struct octoi_account *ac;
+
+ llist_for_each_entry(ac, &srv->cfg.accounts, list) {
+ if (!strcmp(ac->user_id, user_id))
+ return ac;
+ }
+ return NULL;
+}
+
+static struct octoi_server *octoi_server_alloc(void *ctx)
+{
+ struct octoi_server *srv = talloc_zero(ctx, struct octoi_server);
+ if (!srv)
+ return NULL;
+
+ INIT_LLIST_HEAD(&srv->cfg.accounts);
+
+ return srv;
+}
+
+/***********************************************************************
+ * VTY
+ ***********************************************************************/
+
+static struct cmd_node srv_node = {
+ (enum node_type) OCTOI_SRV_NODE,
+ "%s(config-octoi-server)# ",
+ 1,
+};
+
+static struct cmd_node account_node = {
+ (enum node_type) OCTOI_ACCOUNT_NODE,
+ "%s(config-octoi-server-account)# ",
+ 1,
+};
+
+DEFUN(cfg_server, cfg_server_cmd,
+ "octoi-server",
+ "Configure the OCTOI server\n")
+{
+ struct octoi_server *srv = g_octoi->server;
+
+ if (!srv)
+ srv = g_octoi->server = octoi_server_alloc(g_octoi);
+ OSMO_ASSERT(srv);
+
+ vty->node = OCTOI_SRV_NODE;
+ vty->index = srv;
+
+ return CMD_SUCCESS;
+}
+
+#if 0
+DEFUN(cfg_no_server, cfg_no_server_cmd,
+ "no octoi-server",
+ NO_STR "Disable OCTOI server\n")
+{
+ /* we'd need to iterate over all accounts and terminate any
+ * octoi_server_fsm that might exist for each account */
+}
+#endif
+
+DEFUN(cfg_srv_local, cfg_srv_local_cmd,
+ "local-bind (A.B.C.D|X:X::X:X) <0-65535>",
+ "Local OCTOI socket bind address/port\n"
+ "Local OCTOI IPv4 Address\n"
+ "Local OCTOI IPv6 Address\n"
+ "Local OCTOI UDP Port Number\n")
+{
+ struct octoi_server *srv = vty->index;
+ int rc;
+
+ rc = osmo_sockaddr_str_from_str(&srv->cfg.local, argv[0], atoi(argv[1]));
+ if (rc < 0) {
+ vty_out(vty, "%% sockaddr Error: %s%s", strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (srv->sock)
+ octoi_sock_destroy(srv->sock);
+
+ srv->sock = octoi_sock_create_server(srv, srv, &srv->cfg.local);
+ if (!srv->sock) {
+ vty_out(vty, "%% failed to create/bind socket: %s%s", strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ srv->sock->rx_cb = octoi_srv_fsm_rx_cb;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_srv_account, cfg_srv_account_cmd,
+ "account USER_ID",
+ "Configure a local user account\n"
+ "User ID\n")
+{
+ struct octoi_server *srv = vty->index;
+ const char *user_id = argv[0];
+ struct octoi_account *ac = octoi_account_find(srv, user_id);
+ if (!ac)
+ ac = octoi_server_account_create(srv, user_id);
+ if (!ac)
+ return CMD_WARNING;
+
+ vty->node = OCTOI_ACCOUNT_NODE;
+ vty->index = ac;
+
+ return CMD_SUCCESS;
+}
+
+#if 0
+DEFUN(cfg_srv_no_account, cfg_serv_no_account_cmd,
+ "no account USER_ID",
+ NO_STR "Remove a local user account\n"
+ "User ID\n")
+{
+ struct octoi_server *srv = vty->index;
+ const char *user_id = argv[0];
+ struct octoi_account *ac = octoi_account_find(srv, user_id);
+ if (!ac)
+ return CMD_WARNING;
+
+ /* we'd need to iterate all octoi_server_fsm instances and terminate any
+ * pointing to this account */
+}
+#endif
+
+gDEFUN(cfg_account_mode, cfg_account_mode_cmd,
+ "mode (ice1usb|redirect)",
+ "Operational mode of account\n"
+ "Connect to local icE1usb (identified by USB serial + line number)\n"
+ "Redirect to other IP/Port\n")
+{
+ struct octoi_account *acc = vty->index;
+
+ /* leave old mode */
+ switch (acc->mode) {
+ case ACCOUNT_MODE_ICE1USB:
+ talloc_free(acc->u.ice1usb.usb_serial);
+ break;
+ default:
+ break;
+ }
+ memset(&acc->u, 0, sizeof(acc->u));
+
+ if (!strcmp(argv[0], "ice1usb")) {
+ acc->mode = ACCOUNT_MODE_ICE1USB;
+ } else if (!strcmp(argv[0], "redirect")) {
+ acc->mode = ACCOUNT_MODE_REDIRECT;
+ } else
+ OSMO_ASSERT(0);
+
+ return CMD_SUCCESS;
+}
+
+#define ICE1_STR "icE1usb settings\n"
+
+gDEFUN(cfg_account_ice1_serno, cfg_account_ice1_serno_cmd,
+ "ice1usb serial-number SERNO",
+ ICE1_STR "USB Serial Number String\n" "USB Serial Number String\n")
+{
+ struct octoi_account *acc = vty->index;
+
+ if (acc->mode != ACCOUNT_MODE_ICE1USB) {
+ vty_out(vty, "%% Error: Not in icE1usb mode!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ osmo_talloc_replace_string(acc, &acc->u.ice1usb.usb_serial, argv[0]);
+ return CMD_SUCCESS;
+}
+
+gDEFUN(cfg_account_ice1_line, cfg_account_ice1_line_cmd,
+ "ice1usb line-number <0-1>",
+ ICE1_STR "E1 Line number\n" "E1 Line number\n")
+{
+ struct octoi_account *acc = vty->index;
+
+ if (acc->mode != ACCOUNT_MODE_ICE1USB) {
+ vty_out(vty, "%% Error: Not in icE1usb mode!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ acc->u.ice1usb.line_nr = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_account_redir, cfg_account_redir_cmd,
+ "redirect (A.B.C.D|X:X::X:X) <0-65535>",
+ "Redirect to other IP/Port\n"
+ "Destination IPv4 address\n"
+ "Destination IPv6 address\n"
+ "Destination UDP port\n")
+{
+ struct octoi_account *acc = vty->index;
+ int rc;
+
+ if (acc->mode != ACCOUNT_MODE_REDIRECT) {
+ vty_out(vty, "%% Error: Not in redirect mode!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ rc = osmo_sockaddr_str_from_str(&acc->u.redirect.to, argv[0], atoi(argv[1]));
+ if (rc < 0) {
+ vty_out(vty, "%% sockaddr Error: %s%s", strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+void octoi_vty_write_one_account(struct vty *vty, const struct octoi_account *acc)
+{
+ if (!acc)
+ return;
+
+ vty_out(vty, " account %s%s", acc->user_id, VTY_NEWLINE);
+ vty_out(vty, " mode %s%s", get_value_string(octoi_account_mode_name, acc->mode),
+ VTY_NEWLINE);
+
+ switch (acc->mode) {
+ case ACCOUNT_MODE_NONE:
+ break;
+ case ACCOUNT_MODE_ICE1USB:
+ if (acc->u.ice1usb.usb_serial)
+ vty_out(vty, " ice1usb serial-number %s%s", acc->u.ice1usb.usb_serial,
+ VTY_NEWLINE);
+
+ vty_out(vty, " ice1usb line-number %u%s", acc->u.ice1usb.line_nr, VTY_NEWLINE);
+ break;
+ case ACCOUNT_MODE_REDIRECT:
+ vty_out(vty, " redirect %s %u%s", acc->u.redirect.to.ip, acc->u.redirect.to.port,
+ VTY_NEWLINE);
+ break;
+ case ACCOUNT_MODE_DAHDI:
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+static int config_write_octoi_srv(struct vty *vty)
+{
+ struct octoi_account *acc;
+ struct octoi_server *srv = g_octoi->server;
+
+ if (!srv)
+ return 0;
+
+ vty_out(vty, "octoi-server%s", VTY_NEWLINE);
+ if (strlen(srv->cfg.local.ip)) {
+ vty_out(vty, " local-bind %s %u%s", srv->cfg.local.ip, srv->cfg.local.port,
+ VTY_NEWLINE);
+ }
+
+ llist_for_each_entry(acc, &srv->cfg.accounts, list)
+ octoi_vty_write_one_account(vty, acc);
+
+ return 0;
+}
+
+DEFUN(show_server, show_server_cmd,
+ "show octoi-server",
+ SHOW_STR "Display information about the OCTOI Server\n")
+{
+ struct octoi_server *srv = g_octoi->server;
+ struct octoi_sock *sock = srv->sock;
+
+ vty_show_octoi_sock(vty, sock);
+
+ return CMD_SUCCESS;
+}
+
+void octoi_server_vty_init(void)
+{
+
+ install_element_ve(&show_server_cmd);
+
+ install_node(&account_node, NULL);
+ install_element(OCTOI_ACCOUNT_NODE, &cfg_account_mode_cmd);
+ install_element(OCTOI_ACCOUNT_NODE, &cfg_account_ice1_serno_cmd);
+ install_element(OCTOI_ACCOUNT_NODE, &cfg_account_ice1_line_cmd);
+ install_element(OCTOI_ACCOUNT_NODE, &cfg_account_redir_cmd);
+
+ install_node(&srv_node, config_write_octoi_srv);
+ install_element(CONFIG_NODE, &cfg_server_cmd);
+ //install_element(CONFIG_NODE, &cfg_no_server_cmd);
+ install_element(OCTOI_SRV_NODE, &cfg_srv_local_cmd);
+ install_element(OCTOI_SRV_NODE, &cfg_srv_account_cmd);
+ //install_element(CONFIG_SRV_NODE, &cfg_srv_no_account_cmd);
+}
diff --git a/src/octoi/octoi_vty.h b/src/octoi/octoi_vty.h
new file mode 100644
index 0000000..6f18802
--- /dev/null
+++ b/src/octoi/octoi_vty.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <osmocom/vty/command.h>
+
+#include "octoi.h"
+
+extern struct cmd_element cfg_account_mode_cmd;
+extern struct cmd_element cfg_account_ice1_serno_cmd;
+extern struct cmd_element cfg_account_ice1_line_cmd;
+
+struct octoi_account *octoi_client_account_create(struct octoi_client *clnt, const char *user_id);
+
+void octoi_vty_write_one_account(struct vty *vty, const struct octoi_account *acc);
+
+void vty_show_octoi_sock(struct vty *vty, struct octoi_sock *sock);
diff --git a/src/osmo-e1d.c b/src/osmo-e1d.c
index f86a92a..1e18f88 100644
--- a/src/osmo-e1d.c
+++ b/src/osmo-e1d.c
@@ -39,11 +39,14 @@
#include <osmocom/core/select.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/logging.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/misc.h>

#include <osmocom/e1d/proto_srv.h>
#include <osmocom/e1d/proto.h>

#include "e1d.h"
+#include <osmocom/octoi/octoi.h>
#include "usb.h"
#include "log.h"

@@ -83,6 +86,7 @@
static struct vty_app_info vty_info = {
.name = "osmo-e1d",
.version = PACKAGE_VERSION,
+ .go_parent_cb = e1d_vty_go_parent,
.copyright =
"(C) 2019-2022 by Sylvain Munaut, Harald Welte and contributors\r\n",
"License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl-2.0.html>\r\n"
@@ -176,7 +180,12 @@
vty_init(&vty_info);
logging_vty_add_cmds();
e1d_vty_init(e1d);
+ octoi_init(g_e1d_ctx, e1d, &e1d_octoi_ops);
rate_ctr_init(e1d);
+ osmo_stat_item_init(e1d);
+ osmo_stats_vty_add_cmds();
+ osmo_talloc_vty_add_cmds();
+ osmo_fsm_vty_add_cmds();

handle_options(argc, argv);

diff --git a/src/usb.c b/src/usb.c
index 40518d8..1949485 100644
--- a/src/usb.c
+++ b/src/usb.c
@@ -939,6 +939,8 @@
if (line_data->ep_int)
resubmit_irq(line);

+ e1_line_active(line);
+
next_interface:
line_nr++;
}
diff --git a/src/vty.c b/src/vty.c
index bdd0457..ba6b1ac 100644
--- a/src/vty.c
+++ b/src/vty.c
@@ -24,6 +24,7 @@
#include <stdbool.h>
#include <unistd.h>
#include <time.h>
+#include <errno.h>

#include <osmocom/core/linuxlist.h>

@@ -36,7 +37,9 @@
#include <osmocom/vty/misc.h>
#include <osmocom/vty/tdef_vty.h>

+#include <osmocom/octoi/octoi.h>
#include <osmocom/e1d/proto.h>
+
#include "e1d.h"
#include "usb.h"

@@ -60,6 +63,23 @@
1,
};

+int e1d_vty_go_parent(struct vty *vty)
+{
+ struct e1_line *line;
+
+ switch (vty->node) {
+ case LINE_NODE:
+ line = vty->index;
+ vty->node = INTF_NODE;
+ vty->index = line->intf;
+ break;
+ default:
+ return octoi_vty_go_parent(vty);
+ }
+
+ return 0;
+}
+
#if 0
static void vty_dump_ts(struct vty *vty, const struct e1_ts *ts)
{
@@ -135,6 +155,7 @@
const struct value_string e1_line_mode_names[] = {
{ E1_LINE_MODE_CHANNELIZED, "channelized" },
{ E1_LINE_MODE_SUPERCHANNEL, "superchannel" },
+ { E1_LINE_MODE_E1OIP, "e1oip" },
{ 0, NULL }
};

@@ -294,14 +315,17 @@
}

DEFUN(cfg_e1d_if_line_mode, cfg_e1d_if_line_mode_cmd,
- "mode (channelized|superchannel)",
+ "mode (channelized|superchannel|e1oip)",
"Configure the mode of the E1 line\n"
"Channelized (64kBps timeslot) mode\n"
"Superchannel (1xHDLC over 31x64kBps) mode\n")
{
struct e1_line *line = vty->index;
-
- line->mode = get_string_value(e1_line_mode_names, argv[0]);
+ enum e1_line_mode new_mode = get_string_value(e1_line_mode_names, argv[0]);
+ if (line->mode != new_mode) {
+ /* FIXME: clean up any old state */
+ line->mode = new_mode;
+ }
return CMD_SUCCESS;
}


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

Gerrit-Project: osmo-e1d
Gerrit-Branch: master
Gerrit-Change-Id: I05f5ff697ca8f7dccdcf89660f12089babfcc92e
Gerrit-Change-Number: 27588
Gerrit-PatchSet: 5
Gerrit-Owner: laforge <laforge@osmocom.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge@osmocom.org>
Gerrit-Reviewer: manawyrm <osmocom.account@tbspace.de>
Gerrit-Reviewer: tnt <tnt@246tNt.com>
Gerrit-MessageType: merged