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
This commit is contained in:
Harald Welte 2022-01-19 21:10:52 +01:00
parent 8ec461ebe8
commit e324507676
36 changed files with 3427 additions and 12 deletions

View File

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

View File

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

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

5
debian/libosmo-octoi-dev.install vendored Normal file
View File

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

1
debian/libosmo-octoi0.install vendored Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

11
libosmo-octoi.pc.in Normal file
View File

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

View File

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

View File

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

View File

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

152
src/e1oip.c Normal file
View File

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

View File

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

View File

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

37
src/octoi/Makefile.am Normal file
View File

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

281
src/octoi/e1oip.c Normal file
View File

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

61
src/octoi/e1oip.h Normal file
View File

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

146
src/octoi/frame_fifo.c Normal file
View File

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

42
src/octoi/frame_fifo.h Normal file
View File

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

View File

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

131
src/octoi/octoi.c Normal file
View File

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

26
src/octoi/octoi.h Normal file
View File

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

320
src/octoi/octoi_clnt_fsm.c Normal file
View File

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

219
src/octoi/octoi_clnt_vty.c Normal file
View File

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

226
src/octoi/octoi_fsm.c Normal file
View File

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

31
src/octoi/octoi_fsm.h Normal file
View File

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

485
src/octoi/octoi_sock.c Normal file
View File

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

97
src/octoi/octoi_sock.h Normal file
View File

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

379
src/octoi/octoi_srv_fsm.c Normal file
View File

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

386
src/octoi/octoi_srv_vty.c Normal file
View File

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

15
src/octoi/octoi_vty.h Normal file
View File

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

View File

@ -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 void sig_handler(int signo)
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 @@ int main(int argc, char *argv[])
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);

View File

@ -939,6 +939,8 @@ _e1_usb_open_device(struct e1_daemon *e1d, struct libusb_device *dev)
if (line_data->ep_int)
resubmit_irq(line);
e1_line_active(line);
next_interface:
line_nr++;
}

View File

@ -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 @@ static struct cmd_node line_node = {
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_ts_mode_names[] = {
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, cfg_e1d_if_line_cmd, "line <0-255>",
}
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;
}