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) $(NULL)
pkgconfigdir = $(libdir)/pkgconfig pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = libosmo-e1d.pc pkgconfig_DATA = libosmo-e1d.pc libosmo-octoi.pc
AM_DISTCHECK_CONFIGURE_FLAGS = \ AM_DISTCHECK_CONFIGURE_FLAGS = \
--with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir) --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir)

View File

@ -97,6 +97,8 @@ AC_OUTPUT(
doc/Makefile doc/Makefile
doc/examples/Makefile doc/examples/Makefile
src/Makefile src/Makefile
src/octoi/Makefile
include/Makefile include/Makefile
libosmo-e1d.pc 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}), libosmo-e1d1 (= ${binary:Version}),
libosmocore-dev, libosmocore-dev,
Description: Development headers for the osmo-e1d library. 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_OFF = 0x00,
E1DP_LMODE_CHANNELIZED = 0x20, E1DP_LMODE_CHANNELIZED = 0x20,
E1DP_LMODE_SUPERCHANNEL = 0x21, E1DP_LMODE_SUPERCHANNEL = 0x21,
E1DP_LMODE_E1OIP = 0x22,
}; };
enum osmo_e1dp_ts_mode { 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. # This is _NOT_ the library release version, it's an API version.
# Please read Chapter 6 "Library interface versions" of the libtool # Please read Chapter 6 "Library interface versions" of the libtool
# documentation before making any modification # documentation before making any modification
@ -47,10 +49,12 @@ osmo_e1d_SOURCES = \
usb.c \ usb.c \
vpair.c \ vpair.c \
vty.c \ vty.c \
e1oip.c \
$(NULL) $(NULL)
osmo_e1d_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) \ 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 = \ osmo_e1d_pipe_SOURCES = \
e1d-ts-pipe.c \ e1d-ts-pipe.c \
@ -60,7 +64,8 @@ osmo_e1d_pipe_LDADD = $(LIBOSMOCORE_LIBS) libosmo-e1d.la
osmo_e1gen_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) \ osmo_e1gen_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) \
$(LIBOSMOUSB_LIBS) $(LIBUSB_LIBS) $(LIBOSMOUSB_LIBS) $(LIBUSB_LIBS) \
octoi/libosmo-octoi.la
osmo_e1gen_SOURCES = \ osmo_e1gen_SOURCES = \
intf_line.c \ 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: case E1_LINE_MODE_SUPERCHANNEL:
li->cfg.mode = E1DP_LMODE_SUPERCHANNEL; li->cfg.mode = E1DP_LMODE_SUPERCHANNEL;
break; break;
case E1_LINE_MODE_E1OIP:
li->cfg.mode = E1DP_LMODE_E1OIP;
break;
default: default:
OSMO_ASSERT(0); OSMO_ASSERT(0);
} }

View File

@ -31,8 +31,13 @@
#include <osmocom/core/timer.h> #include <osmocom/core/timer.h>
#include <osmocom/vty/command.h> #include <osmocom/vty/command.h>
#include <osmocom/octoi/octoi.h>
#include <osmocom/e1d/proto.h> #include <osmocom/e1d/proto.h>
/***********************************************************************
* core e1d related data structures
***********************************************************************/
enum e1d_vty_node { enum e1d_vty_node {
E1D_NODE = _LAST_OSMOVTY_NODE + 1, E1D_NODE = _LAST_OSMOVTY_NODE + 1,
INTF_NODE, 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 /* 1 channel group spanning all 31 TS, as used e.g. when using Frame Relay
* or raw HDLC over channelized E1. */ * or raw HDLC over channelized E1. */
E1_LINE_MODE_SUPERCHANNEL, E1_LINE_MODE_SUPERCHANNEL,
/* E1 forwarding over IP */
E1_LINE_MODE_E1OIP,
}; };
#define E1L_TS0_RX_CRC4_ERR 0x01 #define E1L_TS0_RX_CRC4_ERR 0x01
@ -114,6 +121,7 @@ struct e1_line {
struct e1_ts ts[32]; struct e1_ts ts[32];
/* superchannel */ /* superchannel */
struct e1_ts superchan; struct e1_ts superchan;
struct octoi_peer *octoi_peer;
struct { struct {
/*! buffer where we aggregate the E bits each multi-frame */ /*! buffer where we aggregate the E bits each multi-frame */
@ -165,6 +173,7 @@ struct e1_daemon {
struct llist_head interfaces; struct llist_head interfaces;
}; };
extern const struct octoi_ops e1d_octoi_ops;
struct e1_line * struct e1_line *
e1_intf_find_line(struct e1_intf *intf, uint8_t id); 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 void
e1_line_destroy(struct e1_line *line); e1_line_destroy(struct e1_line *line);
void
e1_line_active(struct e1_line *line);
int int
e1_line_mux_out(struct e1_line *line, uint8_t *buf, int fts); 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 * struct e1_intf *
e1d_vpair_intf_peer(struct e1_intf *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 "e1d.h"
#include "log.h" #include "log.h"
#include <osmocom/octoi/octoi.h>
const struct value_string e1_driver_names[] = { const struct value_string e1_driver_names[] = {
{ E1_DRIVER_USB, "usb" }, { 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); line->ctrs = rate_ctr_group_alloc(line, &line_ctrg_desc, intf->id << 8 | line->id);
OSMO_ASSERT(line->ctrs); 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_setup(&line->ts0.timer, _ts0_tmr_cb, line);
osmo_timer_schedule(&line->ts0.timer, 1, 0); osmo_timer_schedule(&line->ts0.timer, 1, 0);
llist_add_tail(&line->list, &intf->lines);
/* start watchdog timer */ /* start watchdog timer */
osmo_timer_setup(&line->watchdog.timer, line_watchdog_cb, line); osmo_timer_setup(&line->watchdog.timer, line_watchdog_cb, line);
osmo_timer_schedule(&line->watchdog.timer, 1, 0); osmo_timer_schedule(&line->watchdog.timer, 1, 0);
LOGPLI(line, DE1D, LOGL_NOTICE, "Created\n"); switch (line->mode) {
case E1_LINE_MODE_E1OIP:
return line; 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 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: case E1_LINE_MODE_SUPERCHANNEL:
_e1_line_mux_out_superchan(line, buf, fts); _e1_line_mux_out_superchan(line, buf, fts);
break; break;
case E1_LINE_MODE_E1OIP:
e1oip_line_mux_out(line, buf, fts);
break;
default: default:
OSMO_ASSERT(0); 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); return _e1_line_demux_in_channelized(line, buf, ftr);
case E1_LINE_MODE_SUPERCHANNEL: case E1_LINE_MODE_SUPERCHANNEL:
return _e1_line_demux_in_superchan(line, buf, ftr); return _e1_line_demux_in_superchan(line, buf, ftr);
case E1_LINE_MODE_E1OIP:
return e1oip_line_demux_in(line, buf, ftr);
default: default:
OSMO_ASSERT(0); 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/core/select.h>
#include <osmocom/vty/telnet_interface.h> #include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/logging.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_srv.h>
#include <osmocom/e1d/proto.h> #include <osmocom/e1d/proto.h>
#include "e1d.h" #include "e1d.h"
#include <osmocom/octoi/octoi.h>
#include "usb.h" #include "usb.h"
#include "log.h" #include "log.h"
@ -83,6 +86,7 @@ static void sig_handler(int signo)
static struct vty_app_info vty_info = { static struct vty_app_info vty_info = {
.name = "osmo-e1d", .name = "osmo-e1d",
.version = PACKAGE_VERSION, .version = PACKAGE_VERSION,
.go_parent_cb = e1d_vty_go_parent,
.copyright = .copyright =
"(C) 2019-2022 by Sylvain Munaut, Harald Welte and contributors\r\n", "(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" "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); vty_init(&vty_info);
logging_vty_add_cmds(); logging_vty_add_cmds();
e1d_vty_init(e1d); e1d_vty_init(e1d);
octoi_init(g_e1d_ctx, e1d, &e1d_octoi_ops);
rate_ctr_init(e1d); 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); 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) if (line_data->ep_int)
resubmit_irq(line); resubmit_irq(line);
e1_line_active(line);
next_interface: next_interface:
line_nr++; line_nr++;
} }

View File

@ -24,6 +24,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <unistd.h> #include <unistd.h>
#include <time.h> #include <time.h>
#include <errno.h>
#include <osmocom/core/linuxlist.h> #include <osmocom/core/linuxlist.h>
@ -36,7 +37,9 @@
#include <osmocom/vty/misc.h> #include <osmocom/vty/misc.h>
#include <osmocom/vty/tdef_vty.h> #include <osmocom/vty/tdef_vty.h>
#include <osmocom/octoi/octoi.h>
#include <osmocom/e1d/proto.h> #include <osmocom/e1d/proto.h>
#include "e1d.h" #include "e1d.h"
#include "usb.h" #include "usb.h"
@ -60,6 +63,23 @@ static struct cmd_node line_node = {
1, 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 #if 0
static void vty_dump_ts(struct vty *vty, const struct e1_ts *ts) 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[] = { const struct value_string e1_line_mode_names[] = {
{ E1_LINE_MODE_CHANNELIZED, "channelized" }, { E1_LINE_MODE_CHANNELIZED, "channelized" },
{ E1_LINE_MODE_SUPERCHANNEL, "superchannel" }, { E1_LINE_MODE_SUPERCHANNEL, "superchannel" },
{ E1_LINE_MODE_E1OIP, "e1oip" },
{ 0, NULL } { 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, 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" "Configure the mode of the E1 line\n"
"Channelized (64kBps timeslot) mode\n" "Channelized (64kBps timeslot) mode\n"
"Superchannel (1xHDLC over 31x64kBps) mode\n") "Superchannel (1xHDLC over 31x64kBps) mode\n")
{ {
struct e1_line *line = vty->index; struct e1_line *line = vty->index;
enum e1_line_mode new_mode = get_string_value(e1_line_mode_names, argv[0]);
line->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; return CMD_SUCCESS;
} }