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
changes/88/27588/5
Harald Welte 10 months ago
parent 8ec461ebe8
commit e324507676
  1. 2
      Makefile.am
  2. 2
      configure.ac
  3. 17
      debian/control
  4. 5
      debian/libosmo-octoi-dev.install
  5. 1
      debian/libosmo-octoi0.install
  6. 3
      include/Makefile.am
  7. 1
      include/osmocom/e1d/proto.h
  8. 126
      include/osmocom/octoi/e1oip_proto.h
  9. 87
      include/osmocom/octoi/octoi.h
  10. 11
      libosmo-octoi.pc.in
  11. 9
      src/Makefile.am
  12. 3
      src/ctl.c
  13. 21
      src/e1d.h
  14. 152
      src/e1oip.c
  15. 59
      src/intf_line.c
  16. 5
      src/mux_demux.c
  17. 37
      src/octoi/Makefile.am
  18. 281
      src/octoi/e1oip.c
  19. 61
      src/octoi/e1oip.h
  20. 146
      src/octoi/frame_fifo.c
  21. 42
      src/octoi/frame_fifo.h
  22. 12
      src/octoi/libosmo-octoi.map
  23. 131
      src/octoi/octoi.c
  24. 26
      src/octoi/octoi.h
  25. 320
      src/octoi/octoi_clnt_fsm.c
  26. 219
      src/octoi/octoi_clnt_vty.c
  27. 226
      src/octoi/octoi_fsm.c
  28. 31
      src/octoi/octoi_fsm.h
  29. 485
      src/octoi/octoi_sock.c
  30. 97
      src/octoi/octoi_sock.h
  31. 379
      src/octoi/octoi_srv_fsm.c
  32. 386
      src/octoi/octoi_srv_vty.c
  33. 15
      src/octoi/octoi_vty.h
  34. 9
      src/osmo-e1d.c
  35. 2
      src/usb.c
  36. 30
      src/vty.c

@ -15,7 +15,7 @@ EXTRA_DIST = \
$(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)

@ -97,6 +97,8 @@ AC_OUTPUT(
doc/Makefile
doc/examples/Makefile
src/Makefile
src/octoi/Makefile
include/Makefile
libosmo-e1d.pc
libosmo-octoi.pc
)

17
debian/control vendored

@ -40,3 +40,20 @@ Depends: ${misc:Depends}, ${shlibs:Depends},
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.

@ -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

@ -0,0 +1 @@
usr/lib/*/libosmo-octoi*.so.*

@ -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

@ -73,6 +73,7 @@ enum osmo_e1dp_line_mode {
E1DP_LMODE_OFF = 0x00,
E1DP_LMODE_CHANNELIZED = 0x20,
E1DP_LMODE_SUPERCHANNEL = 0x21,
E1DP_LMODE_E1OIP = 0x22,
};
enum osmo_e1dp_ts_mode {

@ -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));

@ -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);

@ -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}/

@ -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 @@ osmo_e1d_SOURCES = \
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_e1d_pipe_LDADD = $(LIBOSMOCORE_LIBS) libosmo-e1d.la
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 \

@ -71,6 +71,9 @@ _e1d_fill_line_info(struct osmo_e1dp_line_info *li, struct e1_line *line)
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);
}

@ -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 @@ enum e1_line_mode {
/* 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_line {
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 e1_daemon {
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 @@ e1_line_new(struct e1_intf *intf, int line_id, void *drv_data);
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 @@ e1d_vpair_create(struct e1_daemon *e1d, unsigned int num_lines);
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);

@ -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,
};

@ -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 @@ e1_line_new(struct e1_intf *intf, int line_id, void *drv_data)
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

@ -211,6 +211,9 @@ e1_line_mux_out(struct e1_line *line, uint8_t *buf, int fts)
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 @@ e1_line_demux_in(struct e1_line *line, const uint8_t *buf, int size, int frame_b
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);
}

@ -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

@ -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);
}

@ -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);

@ -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;
}

@ -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);

@ -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: *;
};

@ -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));
}