diff --git a/Makefile.am b/Makefile.am index 3f87824..5027905 100644 --- a/Makefile.am +++ b/Makefile.am @@ -15,7 +15,7 @@ EXTRA_DIST = \ $(NULL) pkgconfigdir = $(libdir)/pkgconfig -pkgconfig_DATA = libosmo-e1d.pc +pkgconfig_DATA = libosmo-e1d.pc libosmo-octoi.pc AM_DISTCHECK_CONFIGURE_FLAGS = \ --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir) diff --git a/configure.ac b/configure.ac index f5c15f2..5a09386 100644 --- a/configure.ac +++ b/configure.ac @@ -97,6 +97,8 @@ AC_OUTPUT( doc/Makefile doc/examples/Makefile src/Makefile + src/octoi/Makefile include/Makefile libosmo-e1d.pc + libosmo-octoi.pc ) diff --git a/debian/control b/debian/control index f1e35b7..4f0109e 100644 --- a/debian/control +++ b/debian/control @@ -40,3 +40,20 @@ Depends: ${misc:Depends}, ${shlibs:Depends}, libosmo-e1d1 (= ${binary:Version}), libosmocore-dev, Description: Development headers for the osmo-e1d library. + +Package: libosmo-octoi0 +Section: libs +Architecture: any +Multi-Arch: same +Depends: ${misc:Depends}, ${shlibs:Depends} +Pre-Depends: ${misc:Pre-Depends} +Description: Library for the Osmocom Community TDMoIP network. + +Package: libosmo-octoi-dev +Section: libdevel +Architecture: any +Multi-Arch: same +Depends: ${misc:Depends}, ${shlibs:Depends}, + libosmo-octoi0 (= ${binary:Version}), + libosmocore-dev, +Description: Development headers for the Osmocom Community TDMoIP library. diff --git a/debian/libosmo-octoi-dev.install b/debian/libosmo-octoi-dev.install new file mode 100644 index 0000000..ac05774 --- /dev/null +++ b/debian/libosmo-octoi-dev.install @@ -0,0 +1,5 @@ +usr/include/osmocom/octoi +usr/lib/*/libosmo-octoi*.a +usr/lib/*/libosmo-octoi*.so +usr/lib/*/libosmo-octoi*.la +usr/lib/*/pkgconfig/libosmo-octoi.pc diff --git a/debian/libosmo-octoi0.install b/debian/libosmo-octoi0.install new file mode 100644 index 0000000..389c462 --- /dev/null +++ b/debian/libosmo-octoi0.install @@ -0,0 +1 @@ +usr/lib/*/libosmo-octoi*.so.* diff --git a/include/Makefile.am b/include/Makefile.am index e74dd6e..3c4cb0d 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -1 +1,2 @@ -nobase_include_HEADERS = osmocom/e1d/proto_clnt.h osmocom/e1d/proto.h osmocom/e1d/proto_srv.h +nobase_include_HEADERS = osmocom/e1d/proto_clnt.h osmocom/e1d/proto.h osmocom/e1d/proto_srv.h \ + osmocom/octoi/octoi.h osmocom/octoi/e1oip_proto.h diff --git a/include/osmocom/e1d/proto.h b/include/osmocom/e1d/proto.h index 3033f11..a5cb8ec 100644 --- a/include/osmocom/e1d/proto.h +++ b/include/osmocom/e1d/proto.h @@ -73,6 +73,7 @@ enum osmo_e1dp_line_mode { E1DP_LMODE_OFF = 0x00, E1DP_LMODE_CHANNELIZED = 0x20, E1DP_LMODE_SUPERCHANNEL = 0x21, + E1DP_LMODE_E1OIP = 0x22, }; enum osmo_e1dp_ts_mode { diff --git a/include/osmocom/octoi/e1oip_proto.h b/include/osmocom/octoi/e1oip_proto.h new file mode 100644 index 0000000..96bc1d7 --- /dev/null +++ b/include/osmocom/octoi/e1oip_proto.h @@ -0,0 +1,126 @@ +#pragma once + +struct e1oip_hdr { + uint8_t version:4, + flags:4; + uint8_t msg_type; /* enum e1oip_msgtype */ +} __attribute__ ((packed)); + +#define E1OIP_VERSION 1 + +enum e1oip_msgtype { + E1OIP_MSGT_ECHO_REQ = 0, /* struct e1oip_echo */ + E1OIP_MSGT_ECHO_RESP = 1, /* struct e1oip_echo */ + E1OIP_MSGT_TDM_DATA = 2, /* struct e1oip_tdm_hdr + payload */ + E1OIP_MSGT_SERVICE_REQ = 3, /* struct e1oip_service_req */ + E1OIP_MSGT_SERVICE_ACK = 4, /* struct e1oip_service_ack */ + E1OIP_MSGT_SERVICE_REJ = 5, /* struct e1oip_service_rej */ + E1OIP_MSGT_REDIR_CMD = 6, /* struct e1oip_redir_cmd */ + E1OIP_MSGT_AUTH_REQ = 7, /* struct e1oip_auth_req */ + E1OIP_MSGT_AUTH_RESP = 8, /* struct e1oip_auth_resp */ + E1OIP_MSGT_ERROR_IND = 9, /* struct e1oip_error_ind */ +}; + +enum e1oip_service { + E1OIP_SERVICE_NONE = 0, + E1OIP_SERVICE_E1_FRAMED = 1, /* single (framed) E1 trunk */ +}; + +/* ECHO REQ + ECHO RESP */ +struct e1oip_echo { + /* sequence number to distinguish subsequent requests and + * responses */ + uint16_t seq_nr; + /* data chosen by sender of ECHO_REQ, echoed back in ECHO_RESP */ + uint8_t data[0]; +} __attribute__ ((packed)); + + +/* follows e1oip_hdr for E1OIP_MSGT_TDM_DATA */ +struct e1oip_tdm_hdr { + /* reduced frame number, increments with every E1 frame (8000 + * Hz). 16bit provides > 8s of wrap-around time, which is more + * than sufficient for detecting re-ordered framses over any + * meaningful interval */ + uint16_t frame_nr; + /* bit-mask of timeslots with data contained in 'data' below*/ + uint32_t ts_mask; + /* timeslot data: array of bytes per frame; each frame having + * one octet for the active timeslots indicated in 'ts_mask', + * in ascending order. If ts_mask == 0, then we have a single + * octet in 'data' specifying the number of frames expressed in + * this packet. */ + uint8_t data[0]; +} __attribute__ ((packed)); + +/* client says "hello" to server + requests a service */ +struct e1oip_service_req { + uint32_t requested_service; + char subscriber_id[32]; + char software_id[32]; + char software_version[16]; + uint32_t capability_flags; +} __attribute__ ((packed)); + +/* server instructs client to use other server IP/port */ +struct e1oip_redir_cmd { + char server_ip[40]; /* IPv4 or IPv6 */ + uint16_t server_port; /* UDP port number */ +} __attribute__ ((packed)); + +/* server requests client to authenticate */ +struct e1oip_auth_req { + uint8_t rand_len; + uint8_t rand[16]; + uint8_t autn_len; + uint8_t autn[16]; +} __attribute__ ((packed)); + +/* client responds to auth request. + * - res_len == 0 && auts_len == 0 -> failure + * - res_len != 0 && auts_len == 0 -> success + * - res_len == 0 && auts_len != 0 -> re-sync */ +struct e1oip_auth_resp { + uint8_t res_len; /* RES in success case */ + uint8_t res[16]; + uint8_t auts_len; /* AUTS in resync case */ + uint8_t auts[16]; +} __attribute__ ((packed)); + +/* server acknowledges a client "hello" + service granted */ +struct e1oip_service_ack { + uint32_t assigned_service; + char server_id[32]; + char software_id[32]; + char software_version[16]; + uint32_t capability_flags; /* server supported capabilities */ +} __attribute__ ((packed)); + +/* server acknowledges a client "hello" + service rejected */ +struct e1oip_service_rej { + uint32_t rejected_service; + char reject_message[64]; +} __attribute__ ((packed)); + +/* either side informs the other of an erroneous condition */ +struct e1oip_error_ind { + uint32_t cause; + char error_message[64]; + uint8_t original_message[0]; +} __attribute__ ((packed)); + + +struct e1oip_msg { + struct e1oip_hdr hdr; + union { + struct e1oip_echo echo; + struct e1oip_tdm_hdr tdm_hdr; + struct e1oip_service_req service_req; + struct e1oip_redir_cmd redir_cmd; + struct e1oip_auth_req auth_req; + struct e1oip_auth_resp auth_resp; + struct e1oip_service_ack service_ack; + struct e1oip_service_rej service_rej; + struct e1oip_error_ind error_ind; + } u; +} __attribute__ ((packed)); diff --git a/include/osmocom/octoi/octoi.h b/include/osmocom/octoi/octoi.h new file mode 100644 index 0000000..48ec122 --- /dev/null +++ b/include/osmocom/octoi/octoi.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include +#include + +struct octoi_peer; + +enum octoi_account_mode { + ACCOUNT_MODE_NONE, + ACCOUNT_MODE_ICE1USB, + ACCOUNT_MODE_REDIRECT, + ACCOUNT_MODE_DAHDI, +}; + +extern const struct value_string octoi_account_mode_name[]; + +/* a single user account connecting via OCTOI protocol */ +struct octoi_account { + struct llist_head list; /* member in octoi_server.cfg.accounts */ + char *user_id; /* user ID (IMSI) */ + enum octoi_account_mode mode; + union { + struct { + char *usb_serial; /* USB serial string (ASCII) of icE1usb */ + uint8_t line_nr; /* line nubmer inside icE1usb */ + } ice1usb; + struct { + struct osmo_sockaddr_str to; /* remote IP/port to which to redirect */ + } redirect; + struct { + /* TBD */ + } dahdi; + } u; +}; + +struct octoi_sock; + +struct octoi_server { + struct octoi_sock *sock; /* OCTOI UDP server sock representation */ + struct { + struct llist_head accounts; /* list of octoi_account */ + struct osmo_sockaddr_str local; /* local socket bind address/port */ + } cfg; + +}; + +struct octoi_client { + struct llist_head list; /* member in e1_daemon.octoi.clients */ + struct octoi_sock *sock; /* OCTOI UDP server sock representation */ + struct { + struct osmo_sockaddr_str remote; /* remote socket address/port */ + struct osmo_sockaddr_str local; /* local socket bind address/port */ + + struct octoi_account *account; + } cfg; +}; + +/* call-backs from OCTOI library to application */ +struct octoi_ops { + /* server notifies the application that a new client connection has just been accepted */ + void * (*client_connected)(struct octoi_server *srv, struct octoi_peer *peer, + struct octoi_account *acc); + /* OCTOI library notifies the application that a given peer has disconnected */ + void (*peer_disconnected)(struct octoi_peer *peer); +}; + +struct octoi_daemon { + void *priv; + const struct octoi_ops *ops; + struct octoi_server *server; + struct llist_head clients; +}; + +extern struct octoi_daemon *g_octoi; + +void octoi_init(void *ctx, void *priv, const struct octoi_ops *ops); + +int octoi_vty_go_parent(struct vty *vty); + +struct octoi_peer *octoi_client_get_peer(struct octoi_client *client); +void octoi_clnt_start_for_peer(struct octoi_peer *peer, struct octoi_account *acc); + +void octoi_peer_e1o_in(struct octoi_peer *peer, const uint8_t *buf, int ftr); +void octoi_peer_e1t_out(struct octoi_peer *peer, uint8_t *buf, int fts); + diff --git a/libosmo-octoi.pc.in b/libosmo-octoi.pc.in new file mode 100644 index 0000000..94c6a79 --- /dev/null +++ b/libosmo-octoi.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Osmocom Community TDMoIP Library +Description: C Utility Library +Version: @VERSION@ +Requires: libosmocore +Libs: -L${libdir} -losmo-octoi +Cflags: -I${includedir}/ diff --git a/src/Makefile.am b/src/Makefile.am index f4af9f2..5795598 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,3 +1,5 @@ +SUBDIRS = octoi . + # This is _NOT_ the library release version, it's an API version. # Please read Chapter 6 "Library interface versions" of the libtool # documentation before making any modification @@ -47,10 +49,12 @@ osmo_e1d_SOURCES = \ usb.c \ vpair.c \ vty.c \ + e1oip.c \ $(NULL) osmo_e1d_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) \ - $(LIBOSMOUSB_LIBS) $(LIBUSB_LIBS) libosmo-e1d.la + $(LIBOSMOUSB_LIBS) $(LIBUSB_LIBS) libosmo-e1d.la \ + octoi/libosmo-octoi.la osmo_e1d_pipe_SOURCES = \ e1d-ts-pipe.c \ @@ -60,7 +64,8 @@ osmo_e1d_pipe_LDADD = $(LIBOSMOCORE_LIBS) libosmo-e1d.la osmo_e1gen_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) \ - $(LIBOSMOUSB_LIBS) $(LIBUSB_LIBS) + $(LIBOSMOUSB_LIBS) $(LIBUSB_LIBS) \ + octoi/libosmo-octoi.la osmo_e1gen_SOURCES = \ intf_line.c \ diff --git a/src/ctl.c b/src/ctl.c index 7040001..b5fdee4 100644 --- a/src/ctl.c +++ b/src/ctl.c @@ -71,6 +71,9 @@ _e1d_fill_line_info(struct osmo_e1dp_line_info *li, struct e1_line *line) case E1_LINE_MODE_SUPERCHANNEL: li->cfg.mode = E1DP_LMODE_SUPERCHANNEL; break; + case E1_LINE_MODE_E1OIP: + li->cfg.mode = E1DP_LMODE_E1OIP; + break; default: OSMO_ASSERT(0); } diff --git a/src/e1d.h b/src/e1d.h index dbcc047..726696e 100644 --- a/src/e1d.h +++ b/src/e1d.h @@ -31,8 +31,13 @@ #include #include +#include #include +/*********************************************************************** + * core e1d related data structures + ***********************************************************************/ + enum e1d_vty_node { E1D_NODE = _LAST_OSMOVTY_NODE + 1, INTF_NODE, @@ -94,6 +99,8 @@ enum e1_line_mode { /* 1 channel group spanning all 31 TS, as used e.g. when using Frame Relay * or raw HDLC over channelized E1. */ E1_LINE_MODE_SUPERCHANNEL, + /* E1 forwarding over IP */ + E1_LINE_MODE_E1OIP, }; #define E1L_TS0_RX_CRC4_ERR 0x01 @@ -114,6 +121,7 @@ struct e1_line { struct e1_ts ts[32]; /* superchannel */ struct e1_ts superchan; + struct octoi_peer *octoi_peer; struct { /*! buffer where we aggregate the E bits each multi-frame */ @@ -165,6 +173,7 @@ struct e1_daemon { struct llist_head interfaces; }; +extern const struct octoi_ops e1d_octoi_ops; struct e1_line * e1_intf_find_line(struct e1_intf *intf, uint8_t id); @@ -194,6 +203,9 @@ e1_line_new(struct e1_intf *intf, int line_id, void *drv_data); void e1_line_destroy(struct e1_line *line); +void +e1_line_active(struct e1_line *line); + int e1_line_mux_out(struct e1_line *line, uint8_t *buf, int fts); @@ -211,3 +223,12 @@ e1d_vpair_create(struct e1_daemon *e1d, unsigned int num_lines); struct e1_intf * e1d_vpair_intf_peer(struct e1_intf *intf); + +int +e1oip_line_demux_in(struct e1_line *line, const uint8_t *buf, int ftr); + +int +e1oip_line_mux_out(struct e1_line *line, uint8_t *buf, int fts); + +int +e1d_vty_go_parent(struct vty *vty); diff --git a/src/e1oip.c b/src/e1oip.c new file mode 100644 index 0000000..384a91b --- /dev/null +++ b/src/e1oip.c @@ -0,0 +1,152 @@ +/* + * e1oip.c + * + * (C) 2022 by Harald Welte + * + * 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 +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include "e1d.h" +#include "log.h" + +/*********************************************************************** + * internal helper functions + ***********************************************************************/ + +/* convenience helper function finding a e1_line for given serial_str + id */ +static struct e1_line * +find_line_by_usb_serial(struct e1_daemon *e1d, const char *serial_str, uint8_t id) +{ + struct e1_intf *e1i = e1d_find_intf_by_usb_serial(e1d, serial_str); + if (!e1i) + return NULL; + return e1_intf_find_line(e1i, id); +} + +static struct e1_line * +find_line_for_account(struct e1_daemon *e1d, const struct octoi_account *acc) +{ + switch (acc->mode) { + case ACCOUNT_MODE_ICE1USB: + return find_line_by_usb_serial(e1d, acc->u.ice1usb.usb_serial, + acc->u.ice1usb.line_nr); + case ACCOUNT_MODE_DAHDI: + OSMO_ASSERT(0); /* TODO */ + break; + default: + return NULL; + } +} + + +/*********************************************************************** + * e1d integration + ***********************************************************************/ + +/* physical E1 interface has received some E1 frames (E1->IP) */ +int +e1oip_line_demux_in(struct e1_line *line, const uint8_t *buf, int ftr) +{ + OSMO_ASSERT(line->mode == E1_LINE_MODE_E1OIP); + + if (!line->octoi_peer) + return -ENODEV; + + octoi_peer_e1o_in(line->octoi_peer, buf, ftr); + + return 0; +} + +/* physical E1 interface needs some E1 fames (E1<-IP) */ +int +e1oip_line_mux_out(struct e1_line *line, uint8_t *buf, int fts) +{ + OSMO_ASSERT(line->mode == E1_LINE_MODE_E1OIP); + + if (!line->octoi_peer) { + memset(buf, 0xff, 32*fts); + return -ENODEV; + } + + octoi_peer_e1t_out(line->octoi_peer, buf, fts); + + return 0; +} + + +/* OCTOI server FSM has detected an (authenticated) client connection */ +static void * +_e1d_octoi_client_connected_cb(struct octoi_server *srv, struct octoi_peer *peer, + struct octoi_account *acc) +{ + struct e1_daemon *e1d = g_octoi->priv; + struct e1_line *line; + + /* resolve the line for the just-connected subscriber account */ + line = find_line_for_account(e1d, acc); + if (!line) { + LOGP(DE1D, LOGL_NOTICE, "Could not find E1 line for client %s\n", + acc->user_id); + return NULL; + } + + if (line->octoi_peer) { + LOGPLI(line, DE1D, LOGL_NOTICE, "New OCTOI client connection for %s, " + "but we already have a client connection!\n", acc->user_id); + /* FIXME: properly get rid of the old client */ + } + line->octoi_peer = peer; + + LOGPLI(line, DE1D, LOGL_INFO, "New OCTOI client connection for %s\n", acc->user_id); + + return line; +} + +/* OCTOI has detected that a given peer has vanished; delete reference to it */ +static void +_e1d_octoi_peer_disconnected_cb(struct octoi_peer *peer) +{ + struct e1_daemon *e1d = g_octoi->priv; + struct e1_intf *intf; + + llist_for_each_entry(intf, &e1d->interfaces, list) { + struct e1_line *line; + llist_for_each_entry(line, &intf->lines, list) { + if (line->octoi_peer == peer) { + LOGPLI(line, DE1D, LOGL_NOTICE, "Peer disconnected\n"); + line->octoi_peer = NULL; + return; + } + } + } +} + +const struct octoi_ops e1d_octoi_ops = { + .client_connected = &_e1d_octoi_client_connected_cb, + .peer_disconnected = &_e1d_octoi_peer_disconnected_cb, +}; diff --git a/src/intf_line.c b/src/intf_line.c index bd7a393..9b33141 100644 --- a/src/intf_line.c +++ b/src/intf_line.c @@ -40,6 +40,7 @@ #include "e1d.h" #include "log.h" +#include const struct value_string e1_driver_names[] = { { E1_DRIVER_USB, "usb" }, @@ -250,18 +251,66 @@ e1_line_new(struct e1_intf *intf, int line_id, void *drv_data) line->ctrs = rate_ctr_group_alloc(line, &line_ctrg_desc, intf->id << 8 | line->id); OSMO_ASSERT(line->ctrs); + llist_add_tail(&line->list, &intf->lines); + + LOGPLI(line, DE1D, LOGL_NOTICE, "Created\n"); + + return line; +} + +/* find an octoi client (if any) for the given line */ +static struct octoi_client *octoi_client_by_line(struct e1_line *line) +{ + struct octoi_client *clnt; + + llist_for_each_entry(clnt, &g_octoi->clients, list) { + struct octoi_account *acc = clnt->cfg.account; + switch (acc->mode) { + case ACCOUNT_MODE_ICE1USB: + if (!strcmp(line->intf->usb.serial_str, acc->u.ice1usb.usb_serial) && + line->id == acc->u.ice1usb.line_nr) + return clnt; + break; + case ACCOUNT_MODE_NONE: + case ACCOUNT_MODE_REDIRECT: + break; + default: + OSMO_ASSERT(0); + } + } + return NULL; +} + +/* mark given line as 'active' (hardware present + enabled) */ +void +e1_line_active(struct e1_line *line) +{ + struct octoi_client *clnt; + + LOGPLI(line, DE1D, LOGL_NOTICE, "Activated\n"); + osmo_timer_setup(&line->ts0.timer, _ts0_tmr_cb, line); osmo_timer_schedule(&line->ts0.timer, 1, 0); - llist_add_tail(&line->list, &intf->lines); - /* start watchdog timer */ osmo_timer_setup(&line->watchdog.timer, line_watchdog_cb, line); osmo_timer_schedule(&line->watchdog.timer, 1, 0); - LOGPLI(line, DE1D, LOGL_NOTICE, "Created\n"); - - return line; + switch (line->mode) { + case E1_LINE_MODE_E1OIP: + OSMO_ASSERT(!line->octoi_peer); + /* find a client for this line */ + clnt = octoi_client_by_line(line); + if (!clnt) + return; + /* start the peer for this client */ + line->octoi_peer = octoi_client_get_peer(clnt); + OSMO_ASSERT(line->octoi_peer); + octoi_clnt_start_for_peer(line->octoi_peer, clnt->cfg.account); + break; + default: + break; + } } void diff --git a/src/mux_demux.c b/src/mux_demux.c index 3d72f7f..671d6d1 100644 --- a/src/mux_demux.c +++ b/src/mux_demux.c @@ -211,6 +211,9 @@ e1_line_mux_out(struct e1_line *line, uint8_t *buf, int fts) case E1_LINE_MODE_SUPERCHANNEL: _e1_line_mux_out_superchan(line, buf, fts); break; + case E1_LINE_MODE_E1OIP: + e1oip_line_mux_out(line, buf, fts); + break; default: OSMO_ASSERT(0); } @@ -423,6 +426,8 @@ e1_line_demux_in(struct e1_line *line, const uint8_t *buf, int size, int frame_b return _e1_line_demux_in_channelized(line, buf, ftr); case E1_LINE_MODE_SUPERCHANNEL: return _e1_line_demux_in_superchan(line, buf, ftr); + case E1_LINE_MODE_E1OIP: + return e1oip_line_demux_in(line, buf, ftr); default: OSMO_ASSERT(0); } diff --git a/src/octoi/Makefile.am b/src/octoi/Makefile.am new file mode 100644 index 0000000..b77e0b8 --- /dev/null +++ b/src/octoi/Makefile.am @@ -0,0 +1,37 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall -Wextra -Wno-missing-field-initializers -Wno-unused-parameter -Wno-unused-result $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) + +# This is _NOT_ the library release version, it's an API version. +# Please read Chapter 6 "Library interface versions" of the libtool +# documentation before making any modification +LIBVERSION=0:0:0 + +lib_LTLIBRARIES = libosmo-octoi.la + +libosmo_octoi_la_SOURCES = \ + frame_fifo.c \ + e1oip.c \ + octoi.c \ + octoi_sock.c \ + octoi_fsm.c \ + octoi_srv_fsm.c \ + octoi_srv_vty.c \ + octoi_clnt_fsm.c \ + octoi_clnt_vty.c \ + $(NULL) + +libosmo_octoi_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined \ + -Wl,--version-script=$(srcdir)/libosmo-octoi.map +libosmo_octoi_la_LIBADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) + +noinst_HEADERS = \ + e1oip.h \ + frame_fifo.h \ + octoi.h \ + octoi_fsm.h \ + octoi_sock.h \ + octoi_vty.h \ + $(NULL) + +EXTRA_DIST = libosmo-octoi.map diff --git a/src/octoi/e1oip.c b/src/octoi/e1oip.c new file mode 100644 index 0000000..0cb7206 --- /dev/null +++ b/src/octoi/e1oip.c @@ -0,0 +1,281 @@ +/* + * e1oip.c - Actual TDM/E1oIP handling within OCTOI + * + * (C) 2022 by Harald Welte + * + * 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 +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "octoi_sock.h" +#include "frame_fifo.h" +#include "e1oip.h" + +static const struct rate_ctr_desc iline_ctr_description[] = { + [LINE_CTR_E1oIP_UNDERRUN] = { "e1oip:underrun", "Frames missing/substituted in IP->E1 direction"}, + [LINE_CTR_E1oIP_OVERFLOW] = { "e1oip:overflow", "Frames overflowed in IP->E1 direction"}, +}; + +static const struct rate_ctr_group_desc iline_ctrg_desc = { + .group_name_prefix = "e1oip_line", + .group_description = "Counters for E1oIP line", + .class_id = OSMO_STATS_CLASS_GLOBAL, + .num_ctr = ARRAY_SIZE(iline_ctr_description), + .ctr_desc = iline_ctr_description, +}; + +static const struct osmo_stat_item_desc iline_stat_description[] = { + [LINE_STAT_E1oIP_RTT] = { "e1oip:rtt", "Round Trip Time (in ms)" }, + [LINE_STAT_E1oIP_E1O_FIFO] = { "e1oip:e1o_fifo_level", "E1 originated FIFO level" }, + [LINE_STAT_E1oIP_E1T_FIFO] = { "e1oip:e1t_fifo_level", "E1 terminated FIFO level" }, +}; + +static const struct osmo_stat_item_group_desc iline_stats_desc = { + .group_name_prefix = "e1oip_line", + .group_description = "Stat items for E1oIP line", + .class_id = OSMO_STATS_CLASS_GLOBAL, + .num_items = ARRAY_SIZE(iline_stat_description), + .item_desc = iline_stat_description, +}; + + +/* E1 -> IP FIFO threshold reached: send one packet */ +static void fifo_threshold_cb(struct frame_fifo *fifo, unsigned int frames, void *priv) +{ + struct e1oip_line *iline = priv; + struct msgb *msg; + struct e1oip_tdm_hdr *eith; + unsigned int n_frames = fifo->threshold; + uint8_t buf[n_frames][BYTES_PER_FRAME]; + const uint8_t *ref_frame; + uint32_t ts_mask = 0; + unsigned int num_ts = 0; + unsigned int i; + uint8_t *cur; + int rc; + + msg = msgb_alloc_c(iline, 2048, "E1oIP UDP Tx"); + OSMO_ASSERT(msg); + eith = (struct e1oip_tdm_hdr *) msgb_put(msg, sizeof(*eith)); + + eith->frame_nr = htons(iline->e1o.next_seq); + + //printf("%s: n_frames=%u\n", __func__, n_frames); + + /* first pull all the frames to a local buffer */ + for (i = 0; i < n_frames; i++) { + rc = frame_fifo_out(&iline->e1o.fifo, buf[i]); + if (rc < 0) { + /* this situation cannot really happen: The FIFO called us that + * a certain threshold is reached, ubt now it cannot provide + * frames? */ + LOGPEER(iline->peer, LOGL_ERROR, + "frame_fifo_out failure for frame %u/%u\n", iline->e1o.next_seq + i, i); + } + } + iline_stat_set(iline, LINE_STAT_E1oIP_E1O_FIFO, frame_fifo_frames(&iline->e1o.fifo)); + + /* then compute the ts_mask */ + for (i = 0, ref_frame = iline->e1o.last_frame; i < n_frames; i++, ref_frame = buf[i-1]) { + /* FIXME: what to do about TS0? */ + for (unsigned int j = 1; j < BYTES_PER_FRAME; j++) { + if (buf[i][j] != ref_frame[j]) + ts_mask |= (1U << j); + } + } + eith->ts_mask = htonl(ts_mask); + + for (i = 0; i < BYTES_PER_FRAME; i++) { + if (ts_mask & (1U << i)) + num_ts++; + } + + /* finally, encode the payload */ + if (num_ts == 0) { + /* explicitly encode the number of frames, as the receiver can not determine it + * if we don't include any data */ + msgb_put_u8(msg, n_frames); + } else { + cur = msgb_put(msg, num_ts * n_frames); + for (i = 0; i < n_frames; i++) { + for (unsigned int j = 0; j < BYTES_PER_FRAME; j++) { + if (ts_mask & (1U << j)) + *cur++ = buf[i][j]; + } + } + } + + /* send the packet to the peer */ + octoi_tx(iline->peer, E1OIP_MSGT_TDM_DATA, 0, msgb_data(msg), msgb_length(msg)); + msgb_free(msg); + + /* update the local state */ + iline->e1o.next_seq += n_frames; + if (n_frames) + memcpy(iline->e1o.last_frame, buf[n_frames-1], BYTES_PER_FRAME); +} + +/* build a table indexed by offset inside the EoIP TDM frame resulting in TS number */ +static unsigned int ts_mask2idx(uint8_t *out, uint32_t ts_mask) +{ + unsigned int i; + uint8_t *cur = out; + + memset(out, 0xff, BYTES_PER_FRAME); + + for (i = 0; i < BYTES_PER_FRAME; i++) { + if (ts_mask & (1U << i)) + *cur++ = i; + } + + return (cur - out); +} + +/* An E1OIP_MSGT_TDM_DATA message was received from a remote IP peer */ +int e1oip_rcvmsg_tdm_data(struct e1oip_line *iline, struct msgb *msg) +{ + struct octoi_peer *peer = iline->peer; + const struct e1oip_tdm_hdr *e1th; + uint16_t frame_nr; + uint32_t ts_mask; + uint8_t idx2ts[BYTES_PER_FRAME]; + unsigned int n_frames; + uint8_t frame_buf[BYTES_PER_FRAME]; + unsigned int num_ts; + struct timespec ts; + + /* update the timestamp at which we last received data from this peer */ + clock_gettime(CLOCK_MONOTONIC, &ts); + peer->last_rx_tdm = ts.tv_sec; + + if (!peer->tdm_permitted) + return -EPERM; + + if (msgb_l2len(msg) < sizeof(*e1th)) + return -EINVAL; + + /* read header */ + e1th = (const struct e1oip_tdm_hdr *) msgb_l2(msg); + msg->l3h = msgb_l2(msg) + sizeof(*e1th); + frame_nr = ntohs(e1th->frame_nr); + ts_mask = ntohl(e1th->ts_mask); + + if (frame_nr != iline->e1t.next_seq) { + LOGPEER(peer, LOGL_NOTICE, "RxIP: frame_nr=%u, but expected %u: packet loss? " + "or re-ordering?\n", frame_nr, iline->e1t.next_seq); + /* FIXME: locally substitute frames? */ + } + + /* compute E1oIP idx to timeslot table */ + num_ts = ts_mask2idx(idx2ts, ts_mask); + if (num_ts > 0) { + n_frames = msgb_l3len(msg) / num_ts; + if (msgb_l3len(msg) % num_ts) { + LOGPEER(peer, LOGL_NOTICE, + "RxIP: %u extraneous bytes (len=%u, num_ts=%u, n_frames=%u)\n", + msgb_length(msg) % num_ts, msgb_length(msg), num_ts, n_frames); + } + LOGPEER(peer, LOGL_INFO, "RxIP: frame=%05u ts_mask=0x%08x num_ts=%02u, n_frames=%u\n", + frame_nr, ts_mask, num_ts, n_frames); + } else { + if (msgb_l3len(msg) < 1) { + LOGPEER(peer, LOGL_ERROR, "RxIP: num_ts==0 but no n_frames octet!\n"); + n_frames = BYTES_PER_FRAME; /* hackish assumption */ + } else + n_frames = msg->l3h[0]; + } + + memcpy(frame_buf, iline->e1t.last_frame, BYTES_PER_FRAME); + for (unsigned int i = 0; i < n_frames; i++) { + for (unsigned int j = 0; j < num_ts; j++) { + uint8_t ts_nr = idx2ts[j]; + frame_buf[ts_nr] = e1th->data[i*num_ts + j]; + } + /* FIXME: what to do about TS0? */ + frame_fifo_in(&iline->e1t.fifo, frame_buf); + } + /* update local state */ + memcpy(iline->e1t.last_frame, frame_buf, BYTES_PER_FRAME); + iline->e1t.next_seq = frame_nr + n_frames; + + iline_stat_set(iline, LINE_STAT_E1oIP_E1T_FIFO, frame_fifo_frames(&iline->e1t.fifo)); + + return 0; +} + +/* TODO: more meaningful identifiers? */ +static int g_ctr_idx = 0; + +struct e1oip_line *e1oip_line_alloc(struct octoi_peer *peer) +{ + struct e1oip_line *iline; + int ctr_idx = g_ctr_idx++; + + if (peer->iline) + return NULL; + + iline = talloc_zero(peer, struct e1oip_line); + if (!iline) + return NULL; + + iline->ctrs = rate_ctr_group_alloc(iline, &iline_ctrg_desc, ctr_idx); + iline->stats = osmo_stat_item_group_alloc(iline, &iline_stats_desc, ctr_idx); + + iline->cfg.batching_factor = 32; + iline->cfg.prefill_frame_count = 400; /* 50ms */ + + frame_fifo_init(&iline->e1o.fifo, iline->cfg.batching_factor, fifo_threshold_cb, iline); + memset(&iline->e1o.last_frame, 0xff, sizeof(iline->e1o.last_frame)); + + frame_fifo_init(&iline->e1t.fifo, 0, NULL, iline); + memset(&iline->e1t.last_frame, 0xff, sizeof(iline->e1o.last_frame)); + + iline->peer = peer; + peer->iline = iline; + + return iline; +} + +void e1oip_line_destroy(struct e1oip_line *iline) +{ + if (!iline) + return; + + rate_ctr_group_free(iline->ctrs); + osmo_stat_item_group_free(iline->stats); + if (iline->peer) + iline->peer->iline = NULL; + iline->peer = NULL; + talloc_free(iline); +} diff --git a/src/octoi/e1oip.h b/src/octoi/e1oip.h new file mode 100644 index 0000000..e08e3ca --- /dev/null +++ b/src/octoi/e1oip.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include + +#include + +#include "frame_fifo.h" + +#define iline_ctr_add(iline, idx, add) rate_ctr_add(rate_ctr_group_get_ctr((iline)->ctrs, idx), add) +#define iline_stat_set(iline, idx, add) \ + osmo_stat_item_set(osmo_stat_item_group_get_item((iline)->stats, idx), add) + +enum e1oip_line_ctr { + LINE_CTR_E1oIP_UNDERRUN, + LINE_CTR_E1oIP_OVERFLOW, +}; + +enum e1oip_line_stat { + LINE_STAT_E1oIP_RTT, + LINE_STAT_E1oIP_E1O_FIFO, + LINE_STAT_E1oIP_E1T_FIFO, +}; + +struct octoi_peer; + +struct e1oip_line { + /* back-pointer */ + struct octoi_peer *peer; + + struct rate_ctr_group *ctrs; + struct osmo_stat_item_group *stats; + + /* configuration data */ + struct { + uint8_t batching_factor; + uint32_t prefill_frame_count; + } cfg; + + /* E1 originated side (E1->IP) */ + struct { + struct frame_fifo fifo; + uint8_t last_frame[BYTES_PER_FRAME]; /* last frame on the E1 side */ + uint16_t next_seq; + } e1o; + + /* E1 terminated side (E1<-IP) */ + struct { + struct frame_fifo fifo; + uint8_t last_frame[BYTES_PER_FRAME]; /* last frame on the E1 side */ + uint16_t next_seq; /* next expected sequence nr */ + } e1t; + + /* TODO: statistics (RTT, frame loss, std deviation, alarms */ +}; + +struct e1oip_line *e1oip_line_alloc(struct octoi_peer *peer); +void e1oip_line_destroy(struct e1oip_line *iline); + +int e1oip_rcvmsg_tdm_data(struct e1oip_line *iline, struct msgb *msg); diff --git a/src/octoi/frame_fifo.c b/src/octoi/frame_fifo.c new file mode 100644 index 0000000..6ba6beb --- /dev/null +++ b/src/octoi/frame_fifo.c @@ -0,0 +1,146 @@ +/* + * frame_fifo.c + * + * (C) 2022 by Harald Welte + * + * 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 +#include +#include +#include +#include +#include + +#include + +#include "frame_fifo.h" + + +/*********************************************************************** + * Frame FIFO + ***********************************************************************/ + +void fifo_dump(struct frame_fifo *fifo) +{ + printf("buf=%p, size=%zu, next_in=%lu, next_out=%lu\n", fifo->buf, sizeof(fifo->buf), + fifo->next_in - fifo->buf, fifo->next_out - fifo->buf); +} + +/*! Initialize a frame FIFO. + * \param fifo Caller-allocated memory for FIFO data structure + * \param threshold After how many available frames shall we call threshold_cb + * \param threshold_cb Optional call-back to call whenever FIFO contains more than 'threshold' frames + * \param priv Opaque pointer passed to threshold_cb */ +void frame_fifo_init(struct frame_fifo *fifo, unsigned int threshold, + void (*threshold_cb)(struct frame_fifo *fifo, unsigned int frames, void *priv), + void *priv) +{ + memset(fifo->buf, 0xff, sizeof(fifo->buf)); + fifo->next_in = fifo->buf; + fifo->next_out = fifo->buf; + fifo->threshold = threshold; + fifo->threshold_cb = threshold_cb; + fifo->priv = priv; +} + +#define FIFO_BUF_END(f) ((f)->buf + sizeof((f)->buf)) + +/*! put one received frames into the FIFO. + * \param fifo The FIFO to which we want to put (append) multiple frames + * \param frame Pointer to memory containing the frame data + * \param count Number of frames to put into FIFO. + * \returns 0 on success; -1 on error (overflow */ +int frame_fifo_in(struct frame_fifo *fifo, const uint8_t *frame) +{ + OSMO_ASSERT(fifo->next_in + BYTES_PER_FRAME <= FIFO_BUF_END(fifo)); + + memcpy(fifo->next_in, frame, BYTES_PER_FRAME); + + fifo->next_in += BYTES_PER_FRAME; + if (fifo->next_in >= FIFO_BUF_END(fifo)) + fifo->next_in -= sizeof(fifo->buf); + + /* FIXME: detect overflow */ + + if (fifo->threshold_cb) { + unsigned int frames_avail = frame_fifo_frames(fifo); + if (frames_avail >= fifo->threshold) + fifo->threshold_cb(fifo, frames_avail, fifo->priv); + } + + return 0; +} + +/*! put (append) multiple received frames into the FIFO. + * \param fifo The FIFO to which we want to put (append) multiple frames + * \param frame Pointer to memory containing the frame data + * \param count Number of frames to put into FIFO. + * \returns Number of frames actually put to FIFO; can be less than 'count' */ +int frame_fifo_in_multi(struct frame_fifo *fifo, const uint8_t *frame, size_t count) +{ + const uint8_t *cur = frame; + unsigned int i; + int rc; + + for (i = 0; i < count; i++) { + rc = frame_fifo_in(fifo, cur); + /* abort on the first failing frame, there's no point in trying further */ + if (rc < 0) + return (int) i; + cur += BYTES_PER_FRAME; + } + return (int) i; +} + +/*! pull one frames out of the FIFO. + * \param fifo The FIFO from which we want to pull frames + * \param out Caller-allocated output buffer + * \returns 0 on success; -1 on error (no frame available) */ +int frame_fifo_out(struct frame_fifo *fifo, uint8_t *out) +{ + if (frame_fifo_frames(fifo) < 1) + return -1; + memcpy(out, fifo->next_out, BYTES_PER_FRAME); + fifo->next_out += BYTES_PER_FRAME; + + if (fifo->next_out >= FIFO_BUF_END(fifo)) + fifo->next_out -= sizeof(fifo->buf); + + return 0; +} + +/*! pull multiple frames out of the FIFO. + * \param fifo The FIFO from which we want ot pull frames + * \param out Caller-allocated output buffer + * \param count Number of frames to pull + * \returns number of frames pulled; can be 0 or less than count */ +int frame_fifo_out_multi(struct frame_fifo *fifo, uint8_t *out, size_t count) +{ + uint8_t *cur = out; + unsigned int i; + int rc = 0; + + for (i = 0; i < count; i++) { + rc = frame_fifo_out(fifo, cur); + /* if there's no data in the FIFO, return number of frames + * pulled so far, could be 0. */ + if (rc < 0) + return (int) i; + cur += BYTES_PER_FRAME; + } + return (int) i; +} diff --git a/src/octoi/frame_fifo.h b/src/octoi/frame_fifo.h new file mode 100644 index 0000000..64aa06a --- /dev/null +++ b/src/octoi/frame_fifo.h @@ -0,0 +1,42 @@ +#pragma once + +#define BYTES_PER_FRAME 32 +#define FRAMES_PER_FIFO 800 + +struct frame_fifo { + uint8_t *next_in; /* where to write next input into FIFO */ + uint8_t *next_out; /* where to read next output from FIFO */ + uint8_t threshold; /* number of frames that should trigger */ + void (*threshold_cb)(struct frame_fifo *fifo, unsigned int frames, void *priv); + void *priv; + + uint8_t buf[BYTES_PER_FRAME * FRAMES_PER_FIFO]; +}; + +void frame_fifo_init(struct frame_fifo *fifo, unsigned int threshold, + void (*threshold_cb)(struct frame_fifo *fifo, unsigned int frames, void *priv), + void *priv); + +/* number of frames currently available in FIFO */ +static inline unsigned int frame_fifo_frames(struct frame_fifo *fifo) +{ + return ((fifo->next_in + sizeof(fifo->buf) - fifo->next_out) % sizeof(fifo->buf)) / BYTES_PER_FRAME; +} + +/* for how many frames do we have space in the FIFO? */ +static inline unsigned int frame_fifo_space(struct frame_fifo *fifo) +{ + return FRAMES_PER_FIFO - frame_fifo_frames(fifo); +} + +/* put a received frame into the FIFO */ +int frame_fifo_in(struct frame_fifo *fifo, const uint8_t *frame); + +/* put multiple received frames into the FIFO */ +int frame_fifo_in_multi(struct frame_fifo *fifo, const uint8_t *frame, size_t count); + +/* pull one frame out of the FIFO */ +int frame_fifo_out(struct frame_fifo *fifo, uint8_t *out); + +/* pull multiple frames out of the FIFO */ +int frame_fifo_out_multi(struct frame_fifo *fifo, uint8_t *out, size_t count); diff --git a/src/octoi/libosmo-octoi.map b/src/octoi/libosmo-octoi.map new file mode 100644 index 0000000..01b5167 --- /dev/null +++ b/src/octoi/libosmo-octoi.map @@ -0,0 +1,12 @@ +{ +global: +g_octoi; +octoi_vty_go_parent; +octoi_init; +octoi_client_get_peer; +octoi_clnt_start_for_peer; +octoi_peer_e1o_in; +octoi_peer_e1t_out; + +local: *; +}; diff --git a/src/octoi/octoi.c b/src/octoi/octoi.c new file mode 100644 index 0000000..a2c4ca6 --- /dev/null +++ b/src/octoi/octoi.c @@ -0,0 +1,131 @@ +/* + * octoi.c + * + * (C) 2022 by Harald Welte + * + * 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 +#include + +#include "e1oip.h" +#include "octoi.h" +#include "octoi_sock.h" +#include "frame_fifo.h" + +struct octoi_daemon *g_octoi; + +static struct octoi_client *client4account(struct octoi_account *acc) +{ + struct octoi_client *clnt; + + llist_for_each_entry(clnt, &g_octoi->clients, list) { + if (clnt->cfg.account == acc) + return clnt; + } + + return NULL; +} + +int octoi_vty_go_parent(struct vty *vty) +{ + struct octoi_account *acc; + + switch (vty->node) { + case OCTOI_ACCOUNT_NODE: + vty->node = OCTOI_SRV_NODE; + vty->index = g_octoi->server; + break; + case OCTOI_CLNT_ACCOUNT_NODE: + acc = vty->index; + vty->node = OCTOI_CLNT_NODE; + vty->index = client4account(acc); + break; + default: + vty->node = CONFIG_NODE; + vty->index = NULL; + break; + } + return 0; +} + +void octoi_init(void *ctx, void *priv, const struct octoi_ops *ops) +{ + OSMO_ASSERT(!g_octoi); + + osmo_fsm_register(&octoi_server_fsm); + osmo_fsm_register(&octoi_client_fsm); + + g_octoi = talloc_zero(ctx, struct octoi_daemon); + OSMO_ASSERT(g_octoi); + + g_octoi->priv = priv; + g_octoi->ops = ops; + INIT_LLIST_HEAD(&g_octoi->clients); + + octoi_server_vty_init(); + octoi_client_vty_init(); +} + +/* resolve the octoi_peer for a specified octoi_client */ +struct octoi_peer *octoi_client_get_peer(struct octoi_client *clnt) +{ + return octoi_sock_client_get_peer(clnt->sock); +} + + +/*! E1 interface has received some E1 frames, forward in E1->IP direction. + * \param[in] peer The peer for which E1 frames were received + * \param[in] buf Buffer holding the just-received E1 frames + * \param[in] ftr Number of 32-byte frames in buf */ +void octoi_peer_e1o_in(struct octoi_peer *peer, const uint8_t *buf, int ftr) +{ + struct e1oip_line *iline = peer->iline; + int rc; + + if (!peer->tdm_permitted) + return; + + rc = frame_fifo_in_multi(&iline->e1o.fifo, buf, ftr); + if (rc < ftr) + iline_ctr_add(iline, LINE_CTR_E1oIP_OVERFLOW, ftr - rc); + + iline_stat_set(iline, LINE_STAT_E1oIP_E1O_FIFO, frame_fifo_frames(&iline->e1o.fifo)); +} + +/*! E1 interface needs to transmit some E1 frames, E1<-IP direction. + * \param[in] peer The peer from which E1 frames are needed + * \param[in] buf Caller-provided output buffer to which frames are written. + * \param[in] fts Number of 32-byte frames to be written to buf. */ +void octoi_peer_e1t_out(struct octoi_peer *peer, uint8_t *buf, int fts) +{ + struct e1oip_line *iline = peer->iline; + int rc; + + if (!peer->tdm_permitted) + return; + + for (int i = 0; i < fts; i++) { + uint8_t *cur = buf + BYTES_PER_FRAME*i; + rc = frame_fifo_out(&iline->e1t.fifo, cur); + if (rc < 0) { + iline_ctr_add(iline, LINE_CTR_E1oIP_UNDERRUN, 1); + /* substitute with last received frame */ + memcpy(cur, iline->e1t.last_frame, BYTES_PER_FRAME); + } + } + iline_stat_set(iline, LINE_STAT_E1oIP_E1T_FIFO, frame_fifo_frames(&iline->e1t.fifo)); +} diff --git a/src/octoi/octoi.h b/src/octoi/octoi.h new file mode 100644 index 0000000..589c208 --- /dev/null +++ b/src/octoi/octoi.h @@ -0,0 +1,26 @@ +#pragma once + +/*********************************************************************** + * OCTOI related data structures + ***********************************************************************/ + +#include + +#include + +/* FIXME: migrate to libosmocore/vty/command.h */ +enum octoi_vty_node { + OCTOI_SRV_NODE = RESERVED1_NODE, + OCTOI_ACCOUNT_NODE, + OCTOI_CLNT_NODE, + OCTOI_CLNT_ACCOUNT_NODE, +}; + + +extern struct osmo_fsm octoi_server_fsm; +extern struct osmo_fsm octoi_client_fsm; + +void octoi_server_vty_init(void); +void octoi_client_vty_init(void); + +struct octoi_account *octoi_account_find(struct octoi_server *srv, const char *user_id); diff --git a/src/octoi/octoi_clnt_fsm.c b/src/octoi/octoi_clnt_fsm.c new file mode 100644 index 0000000..1c38bc8 --- /dev/null +++ b/src/octoi/octoi_clnt_fsm.c @@ -0,0 +1,320 @@ +/* + * octoi_clnt_fsm.c - OCTOI Client-side Finite State Machine + * + * (C) 2022 by Harald Welte + * + * 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 +#include + +#include +#include + +#include "octoi_sock.h" +#include "octoi_fsm.h" +#include "e1oip.h" + +enum octoi_client_fsm_state { + CLNT_ST_INIT, + CLNT_ST_SVC_REQ_SENT, + CLNT_ST_ACCEPTED, + CLNT_ST_REJECTED, + CLNT_ST_REDIRECTED, +}; + +struct clnt_state { + struct octoi_peer *peer; + + /* fields filled in locally */ + uint32_t service; + uint32_t capability_flags; + struct osmo_timer_list rx_alive_timer; + struct octoi_account *acc; + + /* fields below are all filled in once received from the remote server side */ + struct { + char *server_id; + char *software_id; + char *software_version; + uint32_t capability_flags; + } remote; +}; + +static void clnt_st_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct clnt_state *st = fi->priv; + + switch (event) { + case OCTOI_CLNT_EV_REQUEST_SERVICE: + octoi_tx_service_req(st->peer, st->service, st->acc->user_id, + PACKAGE_NAME, PACKAGE_VERSION, st->capability_flags); + osmo_fsm_inst_state_chg(fi, CLNT_ST_SVC_REQ_SENT, 10, 0); + break; + default: + OSMO_ASSERT(0); + } +} + +static void clnt_st_svc_req_sent(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct clnt_state *st = fi->priv; + struct msgb *msg = NULL; + //struct e1oip_auth_req *auth_req = NULL; + struct e1oip_service_ack *svc_ack = NULL; + struct e1oip_service_rej *svc_rej = NULL; + //struct e1oip_redir_cmd *redir_cmd = NULL; + + switch (event) { + case OCTOI_CLNT_EV_RX_AUTH_REQ: + msg = data; + //auth_req = msgb_l2(msg); + /* TODO: implement authentication */ + break; + case OCTOI_CLNT_EV_RX_SVC_ACK: + msg = data; + svc_ack = msgb_l2(msg); + osmo_talloc_replace_string(st->peer, &st->remote.server_id, svc_ack->server_id); + osmo_talloc_replace_string(st->peer, &st->remote.software_id, svc_ack->software_id); + osmo_talloc_replace_string(st->peer, &st->remote.software_version, svc_ack->software_version); + LOGPFSML(fi, LOGL_NOTICE, "Rx SERVICE_ACK (service=%u, server_id='%s', software_id='%s', " + "software_version='%s'\n", ntohl(svc_ack->assigned_service), + st->remote.server_id, st->remote.software_id, st->remote.software_version); + osmo_fsm_inst_state_chg(fi, CLNT_ST_ACCEPTED, 0, 0); + break; + case OCTOI_CLNT_EV_RX_SVC_REJ: + msg = data; + svc_rej = msgb_l2(msg); + LOGPFSML(fi, LOGL_NOTICE, "Rx SERVICE_REJ (service=%u, message='%s')\n", + ntohl(svc_rej->rejected_service), svc_rej->reject_message); + osmo_fsm_inst_state_chg(fi, CLNT_ST_REJECTED, 0, 0); + break; + case OCTOI_CLNT_EV_RX_REDIR_CMD: + msg = data; + //redir_cmd = msgb_l2(msg); + LOGPFSML(fi, LOGL_NOTICE, "Rx REDIR_CMD, but not yet supported\n"); + osmo_fsm_inst_state_chg(fi, CLNT_ST_REDIRECTED, 0, 0); + break; + } +} + +static void clnt_st_accepted_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct clnt_state *st = fi->priv; + + st->peer->tdm_permitted = true; + osmo_timer_schedule(&st->rx_alive_timer, 3, 0); +} + +static void clnt_st_accepted(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct clnt_state *st = fi->priv; + + switch (event) { + case OCTOI_CLNT_EV_RX_AUTH_REQ: + /* TODO: implement authentication */ + LOGPFSML(fi, LOGL_NOTICE, "Rx AUTH_REQ, but no authentication supported yet!\n"); + break; + case OCTOI_EV_RX_TDM_DATA: + e1oip_rcvmsg_tdm_data(st->peer->iline, data); + break; + } +} + +static void clnt_st_accepted_onleave(struct osmo_fsm_inst *fi, uint32_t next_state) +{ + struct clnt_state *st = fi->priv; + + osmo_timer_del(&st->rx_alive_timer); + st->peer->tdm_permitted = false; +} + +static void clnt_st_rejected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + LOGPFSML(fi, LOGL_ERROR, "Server has rejected service, will not retry until program restart\n"); +} + +static void clnt_st_redirected(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + //struct clnt_state *st = fi->priv; +} + + +static const struct osmo_fsm_state client_fsm_states[] = { + [CLNT_ST_INIT] = { + .name = "INIT", + .in_event_mask = S(OCTOI_CLNT_EV_REQUEST_SERVICE), + .out_state_mask = S(CLNT_ST_SVC_REQ_SENT), + .action = clnt_st_init, + }, + [CLNT_ST_SVC_REQ_SENT] = { + .name = "SVC_REQ_SENT", + .in_event_mask = S(OCTOI_CLNT_EV_RX_AUTH_REQ) | + S(OCTOI_CLNT_EV_RX_SVC_ACK) | + S(OCTOI_CLNT_EV_RX_SVC_REJ) | + S(OCTOI_CLNT_EV_RX_REDIR_CMD), + .out_state_mask = S(CLNT_ST_SVC_REQ_SENT) | + S(CLNT_ST_ACCEPTED) | + S(CLNT_ST_REJECTED) | + S(CLNT_ST_REDIRECTED), + .action = clnt_st_svc_req_sent, + }, + [CLNT_ST_ACCEPTED] = { + .name = "ACCEPTED", + .in_event_mask = S(OCTOI_CLNT_EV_RX_AUTH_REQ) | + S(OCTOI_EV_RX_TDM_DATA), + .out_state_mask = S(CLNT_ST_INIT), + .action = clnt_st_accepted, + .onenter = clnt_st_accepted_onenter, + .onleave = clnt_st_accepted_onleave, + }, + [CLNT_ST_REJECTED] = { + .name = "REJECTED", + .in_event_mask = 0, + .out_state_mask = 0, + .onenter = clnt_st_rejected_onenter, + }, + [CLNT_ST_REDIRECTED] = { + .name = "REDIRECTED", + .in_event_mask = 0, + .out_state_mask = S(CLNT_ST_SVC_REQ_SENT), + .action = clnt_st_redirected, + }, +}; + +static void clnt_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct clnt_state *st = fi->priv; + struct msgb *msg = data; + struct e1oip_echo *echo_req; + struct e1oip_error_ind *err_ind; + + switch (event) { + case OCTOI_EV_RX_ECHO_REQ: + echo_req = msgb_l2(msg); + octoi_tx_echo_resp(st->peer, ntohs(echo_req->seq_nr), echo_req->data, msgb_l2len(msg)); + break; + case OCTOI_EV_RX_ECHO_RESP: + /* FIXME: update state, peer has responded! */ + break; + case OCTOI_EV_RX_ERROR_IND: + err_ind = msgb_l2(msg); + LOGPFSML(fi, LOGL_ERROR, "Rx OCTOI ERROR IND (cause=0x%08x, msg=%s)\n", + ntohl(err_ind->cause), err_ind->error_message); + break; + default: + OSMO_ASSERT(0); + } +} + +static int clnt_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + struct clnt_state *st = fi->priv; + + switch (fi->state) { + case CLNT_ST_SVC_REQ_SENT: + /* retransmit timer */ + LOGPFSML(fi, LOGL_INFO, "Re-transmitting SERVICE_REQ\n"); + octoi_tx_service_req(st->peer, st->service, st->acc->user_id, + PACKAGE_NAME, PACKAGE_VERSION, st->capability_flags); + osmo_fsm_inst_state_chg(fi, CLNT_ST_SVC_REQ_SENT, 10, 0); + break; + } + return 0; +} + +struct osmo_fsm octoi_client_fsm = { + .name = "OCTOI_CLIENT", + .states = client_fsm_states, + .num_states = ARRAY_SIZE(client_fsm_states), + .allstate_event_mask = S(OCTOI_EV_RX_ECHO_REQ) | + S(OCTOI_EV_RX_ECHO_RESP) | + S(OCTOI_EV_RX_ERROR_IND), + .allstate_action = clnt_allstate_action, + .timer_cb = clnt_fsm_timer_cb, + .log_subsys = DLINP, + .event_names = octoi_fsm_event_names, +}; + +static void clnt_rx_alive_timer_cb(void *data) +{ + struct osmo_fsm_inst *fi = data; + struct clnt_state *st = fi->priv; + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + + if (ts.tv_sec - st->peer->last_rx_tdm > 3) { + LOGPFSML(fi, LOGL_NOTICE, "No TDM data received for >= 3 seconds, declaring peer dead\n"); + osmo_fsm_inst_state_chg(fi, CLNT_ST_INIT, 0, 0); + osmo_fsm_inst_dispatch(fi, OCTOI_CLNT_EV_REQUEST_SERVICE, NULL); + } else + osmo_timer_schedule(&st->rx_alive_timer, 3, 0); +} + + +/* call-back function for every received OCTOI socket message for given peer */ +int octoi_clnt_fsm_rx_cb(struct octoi_peer *peer, struct msgb *msg) +{ + /* ensure peer->priv points to a fsm_inst */ + if (!peer->priv) { + struct osmo_fsm_inst *fi; + struct clnt_state *st; + + fi = osmo_fsm_inst_alloc(&octoi_client_fsm, peer, NULL, LOGL_DEBUG, NULL); + OSMO_ASSERT(fi); + + st = talloc_zero(fi, struct clnt_state); + OSMO_ASSERT(st); + st->peer = peer; + fi->priv = st; + + peer->priv = fi; + } + OSMO_ASSERT(peer->priv); + + return _octoi_fsm_rx_cb(peer, msg); +} + +/* start the OCTO client FSM for a specified peer */ +void octoi_clnt_start_for_peer(struct octoi_peer *peer, struct octoi_account *acc) +{ + OSMO_ASSERT(!peer->sock->cfg.server_mode) + + /* ensure peer->priv points to a fsm_inst */ + if (!peer->priv) { + struct osmo_fsm_inst *fi; + struct clnt_state *st; + + fi = osmo_fsm_inst_alloc(&octoi_client_fsm, peer, NULL, LOGL_DEBUG, acc->user_id); + OSMO_ASSERT(fi); + + st = talloc_zero(fi, struct clnt_state); + OSMO_ASSERT(st); + st->peer = peer; + st->acc = acc; + st->service = E1OIP_SERVICE_E1_FRAMED; + st->capability_flags = 0; + osmo_timer_setup(&st->rx_alive_timer, clnt_rx_alive_timer_cb, fi); + fi->priv = st; + + peer->priv = fi; + } + if (!peer->iline) + peer->iline = e1oip_line_alloc(peer); + + osmo_fsm_inst_dispatch(peer->priv, OCTOI_CLNT_EV_REQUEST_SERVICE, NULL); +} diff --git a/src/octoi/octoi_clnt_vty.c b/src/octoi/octoi_clnt_vty.c new file mode 100644 index 0000000..1f8f7d1 --- /dev/null +++ b/src/octoi/octoi_clnt_vty.c @@ -0,0 +1,219 @@ +/* + * octoi_clnt_vty.c - VTY code for OCTOI client role + * + * (C) 2022 by Harald Welte + * + * 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 +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "octoi.h" +#include "octoi_sock.h" +#include "octoi_fsm.h" +#include "octoi_vty.h" + +/*********************************************************************** + * core data structures + ***********************************************************************/ + +static struct octoi_client *octoi_client_alloc(void *ctx, const char *ip, uint16_t port) +{ + struct octoi_client *clnt = talloc_zero(ctx, struct octoi_client); + int rc; + + if (!clnt) + return NULL; + + rc = osmo_sockaddr_str_from_str(&clnt->cfg.remote, ip, port); + if (rc < 0) { + talloc_free(clnt); + return NULL; + } + + return clnt; +} + +/* find a client for given remote IP + port */ +struct octoi_client *octoi_client_find(const char *ip, uint16_t port) +{ + struct octoi_client *clnt; + + llist_for_each_entry(clnt, &g_octoi->clients, list) { + if (!strcmp(ip, clnt->cfg.remote.ip) && clnt->cfg.remote.port == port) + return clnt; + } + return NULL; +} + +/*********************************************************************** + * VTY + ***********************************************************************/ + +static struct cmd_node clnt_node = { + (enum node_type) OCTOI_CLNT_NODE, + "%s(config-octoi-client)# ", + 1, +}; + +static struct cmd_node clnt_account_node = { + (enum node_type) OCTOI_CLNT_ACCOUNT_NODE, + "%s(config-octoi-client-account)# ", + 1, +}; + +DEFUN(cfg_client, cfg_client_cmd, + "octoi-client (A.B.C.D|X:X::X:X) <0-65535>", + "Configure an OCTOI client\n" + "Remote IPv4 address of OCTOI server\n" + "Remote IPv6 address of OCTOI server\n" + "Remote UDP port number of OCTOI server\n") +{ + const char *ip = argv[0]; + int port = atoi(argv[1]); + struct octoi_client *clnt = octoi_client_find(ip, port); + + if (!clnt) { + clnt = octoi_client_alloc(g_octoi, ip, port); + OSMO_ASSERT(clnt); + llist_add_tail(&clnt->list, &g_octoi->clients); + } + + vty->node = OCTOI_CLNT_NODE; + vty->index = clnt; + + return CMD_SUCCESS; +} + +#if 0 +DEFUN(cfg_no_client, cfg_no_client_cmd, + "no octoi-client (A.B.C.D|X:X::X:X) <0-65535>", + NO_STR "Remove an OCTOI client\n") + "Remote IPv4 address of OCTOI server\n" + "Remote IPv6 address of OCTOI server\n" + "Remote UDP port number of OCTOI server\n") +{ +} +#endif + + +DEFUN(cfg_clnt_local, cfg_clnt_local_cmd, + "local-bind (A.B.C.D|X:X::X:X) <0-65535>", + "Local OCTOI socket bind address/port\n" + "Local OCTOI IPv4 Address\n" + "Local OCTOI IPv6 Address\n" + "Local OCTOI UDP Port Number\n") +{ + struct octoi_client *clnt = vty->index; + int rc; + + rc = osmo_sockaddr_str_from_str(&clnt->cfg.local, argv[0], atoi(argv[1])); + if (rc < 0) { + vty_out(vty, "%% sockaddr Error: %s%s", strerror(errno), VTY_NEWLINE); + return CMD_WARNING; + } + + if (clnt->sock) + octoi_sock_destroy(clnt->sock); + + clnt->sock = octoi_sock_create_client(clnt, clnt, &clnt->cfg.local, &clnt->cfg.remote); + if (!clnt->sock) { + vty_out(vty, "%% failed to create/bind socket: %s%s", strerror(errno), VTY_NEWLINE); + return CMD_WARNING; + } + clnt->sock->rx_cb = octoi_clnt_fsm_rx_cb; + + return CMD_SUCCESS; +} + +DEFUN(cfg_clnt_account, cfg_clnt_account_cmd, + "account USER_ID", + "Configure a local user account\n") +{ + struct octoi_client *clnt = vty->index; + const char *user_id = argv[0]; + struct octoi_account *ac = clnt->cfg.account; + + if (!ac) { + ac = octoi_client_account_create(clnt, user_id); + OSMO_ASSERT(ac); + } else + osmo_talloc_replace_string(ac, &ac->user_id, user_id); + + vty->node = OCTOI_CLNT_ACCOUNT_NODE; + vty->index = ac; + + return CMD_SUCCESS; +} + +DEFUN(show_clnt, show_clnt_cmd, + "show octoi-clients", + SHOW_STR "Display information about the OCTOI Clients\n") +{ + struct octoi_client *clnt; + + llist_for_each_entry(clnt, &g_octoi->clients, list) { + struct octoi_sock *sock = clnt->sock; + + vty_show_octoi_sock(vty, sock); + } + + return CMD_SUCCESS; +} + +static int config_write_octoi_clnt(struct vty *vty) +{ + struct octoi_client *clnt; + + llist_for_each_entry(clnt, &g_octoi->clients, list) { + vty_out(vty, "octoi-client %s %u%s", clnt->cfg.remote.ip, clnt->cfg.remote.port, + VTY_NEWLINE); + if (strlen(clnt->cfg.local.ip)) { + vty_out(vty, " local-bind %s %u%s", clnt->cfg.local.ip, clnt->cfg.local.port, + VTY_NEWLINE); + } + octoi_vty_write_one_account(vty, clnt->cfg.account); + } + + return 0; +} + +void octoi_client_vty_init(void) +{ + install_element_ve(&show_clnt_cmd); + + install_node(&clnt_account_node, NULL); + install_element(OCTOI_CLNT_ACCOUNT_NODE, &cfg_account_ice1_serno_cmd); + install_element(OCTOI_CLNT_ACCOUNT_NODE, &cfg_account_ice1_line_cmd); + install_element(OCTOI_CLNT_ACCOUNT_NODE, &cfg_account_mode_cmd); + + install_node(&clnt_node, config_write_octoi_clnt); + install_element(CONFIG_NODE, &cfg_client_cmd); + install_element(OCTOI_CLNT_NODE, &cfg_clnt_local_cmd); + install_element(OCTOI_CLNT_NODE, &cfg_clnt_account_cmd); +} diff --git a/src/octoi/octoi_fsm.c b/src/octoi/octoi_fsm.c new file mode 100644 index 0000000..3431587 --- /dev/null +++ b/src/octoi/octoi_fsm.c @@ -0,0 +1,226 @@ +/* + * octoi_fsm.c - OCTOI Protocol / Finite State Machine interface + * + * (C) 2022 by Harald Welte + * + * 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 +#include +#include + +#include + +#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 +#include +#include "e1oip.h" + +void vty_show_octoi_sock(struct vty *vty, struct octoi_sock *sock) +{ + struct octoi_peer *peer; + + vty_out(vty, "OCTOI %s Socket on "OSMO_SOCKADDR_STR_FMT"%s", + sock->cfg.server_mode ? "Server" : "Client", + OSMO_SOCKADDR_STR_FMT_ARGS(&sock->cfg.local), VTY_NEWLINE); + + llist_for_each_entry(peer, &sock->peers, list) { + vty_out(vty, " Peer '%s', Remote "OSMO_SOCKADDR_STR_FMT", State %s%s", + peer->name, OSMO_SOCKADDR_STR_FMT_ARGS(&peer->cfg.remote), + osmo_fsm_inst_state_name(peer->priv), VTY_NEWLINE); + vty_out_rate_ctr_group(vty, " ", peer->iline->ctrs); + vty_out_stat_item_group(vty, " ", peer->iline->stats); + } +} diff --git a/src/octoi/octoi_fsm.h b/src/octoi/octoi_fsm.h new file mode 100644 index 0000000..b605c3a --- /dev/null +++ b/src/octoi/octoi_fsm.h @@ -0,0 +1,31 @@ +#pragma once + +enum octoi_fsm_event { + /* common for server and client */ + OCTOI_EV_RX_TDM_DATA, /* receive TDM data from client */ + OCTOI_EV_RX_ECHO_REQ, /* receive echo request from client */ + OCTOI_EV_RX_ECHO_RESP, /* receive echo response from client */ + OCTOI_EV_RX_ERROR_IND, /* receive error indication */ + + /* only on server side */ + OCTOI_SRV_EV_RX_SERVICE_REQ, /* receive service request from client */ + OCTOI_SRV_EV_RX_AUTH_VEC, /* receive auth vector from HLR */ + OCTOI_SRV_EV_RX_AUTH_RESP, /* receive auth response from client */ + + /* only on client side */ + OCTOI_CLNT_EV_REQUEST_SERVICE, + OCTOI_CLNT_EV_RX_AUTH_REQ, + OCTOI_CLNT_EV_RX_SVC_ACK, + OCTOI_CLNT_EV_RX_SVC_REJ, + OCTOI_CLNT_EV_RX_REDIR_CMD, +}; + +#define S(x) (1 << (x)) + +extern const struct value_string octoi_fsm_event_names[]; + +int _octoi_fsm_rx_cb(struct octoi_peer *peer, struct msgb *msg); + +/* call-back function for every received OCTOI socket message for given peer */ +int octoi_srv_fsm_rx_cb(struct octoi_peer *peer, struct msgb *msg); +int octoi_clnt_fsm_rx_cb(struct octoi_peer *peer, struct msgb *msg); diff --git a/src/octoi/octoi_sock.c b/src/octoi/octoi_sock.c new file mode 100644 index 0000000..943270a --- /dev/null +++ b/src/octoi/octoi_sock.c @@ -0,0 +1,485 @@ +/* + * octoi_sock.c - OCTOI Socket handling code + * + * (C) 2022 by Harald Welte + * + * 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 +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "octoi_sock.h" +#include "e1oip.h" + +/*********************************************************************** + * transmit to remote peer + ***********************************************************************/ + +/* transmit something to an octoi peer */ +int octoi_tx(struct octoi_peer *peer, uint8_t msg_type, uint8_t flags, + const void *data, size_t len) +{ + struct e1oip_hdr hdr = { + .version = E1OIP_VERSION, + .flags = flags & 0xf, + .msg_type = msg_type, + }; + struct iovec iov[2] = { + { + .iov_base = (void *) &hdr, + .iov_len = sizeof(hdr), + }, { + .iov_base = (void *) data, + .iov_len = len, + } + }; + struct msghdr msgh = { + .msg_name = &peer->remote, + .msg_namelen = sizeof(peer->remote), + .msg_iov = iov, + .msg_iovlen = ARRAY_SIZE(iov), + .msg_control = NULL, + .msg_controllen = 0, + .msg_flags = 0, + }; + int rc; + + rc = sendmsg(peer->sock->ofd.fd, &msgh, 0); + if (rc < 0) + LOGPEER(peer, LOGL_ERROR, "Error in sendmsg: %s\n", strerror(errno)); + else if (rc != (int) (sizeof(hdr) + len)) + LOGPEER(peer, LOGL_ERROR, "Short write in sendmsg: %d != %zu\n", rc, sizeof(hdr)+len); + + return rc; +} + +static int _octoi_tx_echo(struct octoi_peer *peer, bool is_req, uint16_t seq_nr, + const uint8_t *data, size_t data_len) +{ + enum e1oip_msgtype msgt; + struct { + struct e1oip_echo echo; + uint8_t buf[data_len]; + } u; + + u.echo.seq_nr = htons(seq_nr); + memcpy(u.echo.data, data, data_len); + + if (is_req) + msgt = E1OIP_MSGT_ECHO_REQ; + else + msgt = E1OIP_MSGT_ECHO_RESP; + + return octoi_tx(peer, msgt, 0, &u, sizeof(u)); +} + +int octoi_tx_echo_req(struct octoi_peer *peer, uint16_t seq_nr, const uint8_t *data, size_t data_len) +{ + LOGPEER(peer, LOGL_DEBUG, "Tx ECHO_REQ\n"); + return _octoi_tx_echo(peer, true, seq_nr, data, data_len); +} + +int octoi_tx_echo_resp(struct octoi_peer *peer, uint16_t seq_nr, const uint8_t *data, size_t data_len) +{ + LOGPEER(peer, LOGL_DEBUG, "Tx ECHO_RESP\n"); + return _octoi_tx_echo(peer, false, seq_nr, data, data_len); +} + +int octoi_tx_service_req(struct octoi_peer *peer, uint32_t service, const char *subscr_id, + const char *software_id, const char *software_version, + uint32_t capability_flags) +{ + struct e1oip_service_req service_req; + + memset(&service_req, 0, sizeof(service_req)); + service_req.requested_service = htonl(service); + OSMO_STRLCPY_ARRAY(service_req.subscriber_id, subscr_id); + OSMO_STRLCPY_ARRAY(service_req.software_id, software_id); + OSMO_STRLCPY_ARRAY(service_req.software_version, software_version); + service_req.capability_flags = htonl(capability_flags); + + LOGPEER(peer, LOGL_INFO, "Tx SERVICE_REQ\n"); + return octoi_tx(peer, E1OIP_MSGT_SERVICE_REQ, 0, &service_req, sizeof(service_req)); +} + +int octoi_tx_redir_cmd(struct octoi_peer *peer, const char *server_ip, uint16_t server_port) +{ + struct e1oip_redir_cmd redir; + + memset(&redir, 0, sizeof(redir)); + OSMO_STRLCPY_ARRAY(redir.server_ip, server_ip); + redir.server_port = htons(server_port); + + LOGPEER(peer, LOGL_INFO, "Tx REDIR_CMD\n"); + return octoi_tx(peer, E1OIP_MSGT_REDIR_CMD, 0, &redir, sizeof(redir)); +} + +int octoi_tx_auth_req(struct octoi_peer *peer, uint8_t rand_len, const uint8_t *rand, + uint8_t autn_len, const uint8_t *autn) +{ + struct e1oip_auth_req areq; + + memset(&areq, 0, sizeof(areq)); + + OSMO_ASSERT(rand_len <= sizeof(areq.rand)); + OSMO_ASSERT(autn_len <= sizeof(areq.autn)); + + areq.rand_len = rand_len; + memcpy(areq.rand, rand, rand_len); + areq.autn_len = autn_len; + memcpy(areq.autn, autn, autn_len); + + LOGPEER(peer, LOGL_INFO, "Tx AUTH_REQ\n"); + return octoi_tx(peer, E1OIP_MSGT_AUTH_REQ, 0, &areq, sizeof(areq)); +} + + +int octoi_tx_auth_resp(struct octoi_peer *peer, uint8_t res_len, const uint8_t *res, + uint8_t auts_len, const uint8_t *auts) +{ + struct e1oip_auth_resp aresp; + + memset(&aresp, 0, sizeof(aresp)); + + OSMO_ASSERT(res_len <= sizeof(aresp.res)); + OSMO_ASSERT(auts_len <= sizeof(aresp.auts)); + + aresp.res_len = res_len; + memcpy(aresp.res, res, res_len); + aresp.auts_len = auts_len; + memcpy(aresp.auts, auts, auts_len); + + LOGPEER(peer, LOGL_INFO, "Tx AUTH_RESP\n"); + return octoi_tx(peer, E1OIP_MSGT_AUTH_RESP, 0, &aresp, sizeof(aresp)); +} + +int octoi_tx_service_ack(struct octoi_peer *peer, uint32_t assigned_service, + const char *server_id, const char *software_id, + const char *software_version, uint32_t capability_flags) +{ + struct e1oip_service_ack service_ack; + + memset(&service_ack, 0, sizeof(service_ack)); + service_ack.assigned_service = htonl(assigned_service); + OSMO_STRLCPY_ARRAY(service_ack.server_id, server_id); + OSMO_STRLCPY_ARRAY(service_ack.software_id, software_id); + OSMO_STRLCPY_ARRAY(service_ack.software_version, software_version); + service_ack.capability_flags = htonl(capability_flags); + + LOGPEER(peer, LOGL_INFO, "Tx SERVICE_ACK\n"); + return octoi_tx(peer, E1OIP_MSGT_SERVICE_ACK, 0, &service_ack, sizeof(service_ack)); +} + +int octoi_tx_service_rej(struct octoi_peer *peer, uint32_t rejected_service, const char *message) +{ + struct e1oip_service_rej service_rej; + + memset(&service_rej, 0, sizeof(service_rej)); + service_rej.rejected_service = htonl(rejected_service); + OSMO_STRLCPY_ARRAY(service_rej.reject_message, message); + + LOGPEER(peer, LOGL_INFO, "Tx SERVICE_REJ\n"); + return octoi_tx(peer, E1OIP_MSGT_SERVICE_REJ, 0, &service_rej, sizeof(service_rej)); +} + +int octoi_tx_error_ind(struct octoi_peer *peer, uint32_t cause, const char *message, + const uint8_t *orig, size_t orig_len) +{ + struct { + struct e1oip_error_ind error_ind; + uint8_t orig[orig_len]; + } u; + + u.error_ind.cause = htonl(cause); + OSMO_STRLCPY_ARRAY(u.error_ind.error_message, message); + memcpy(&u.orig, orig, orig_len); + + LOGPEER(peer, LOGL_INFO, "Tx ERROR_IND\n"); + return octoi_tx(peer, E1OIP_MSGT_ERROR_IND, 0, &u, sizeof(u)); +} + + +/*********************************************************************** + * socket + ***********************************************************************/ + +static int sockaddr_cmp(const struct sockaddr *x, const struct sockaddr *y) +{ + if (x->sa_family != y->sa_family) + return -1; + + if (x->sa_family == AF_UNIX) { + const struct sockaddr_un *xun = (void *)x, *yun = (void *)y; + int r = strcmp(xun->sun_path, yun->sun_path); + if (r != 0) + return r; + } else if (x->sa_family == AF_INET) { + const struct sockaddr_in *xin = (void *)x, *yin = (void *)y; + if (xin->sin_addr.s_addr != yin->sin_addr.s_addr) + return -1; + if (xin->sin_port != yin->sin_port) + return -1; + } else if (x->sa_family == AF_INET6) { + const struct sockaddr_in6 *xin6 = (void *)x, *yin6 = (void *)y; + int r = memcmp(xin6->sin6_addr.s6_addr, yin6->sin6_addr.s6_addr, sizeof(xin6->sin6_addr.s6_addr)); + if (r != 0) + return r; + if (xin6->sin6_port != yin6->sin6_port) + return -1; + if (xin6->sin6_flowinfo != yin6->sin6_flowinfo) + return -1; + if (xin6->sin6_scope_id != yin6->sin6_scope_id) + return -1; + } else { + OSMO_ASSERT(0); + } + return 0; +} + +static struct octoi_peer *find_peer_by_sockaddr(struct octoi_sock *sock, const struct sockaddr *sa) +{ + struct octoi_peer *peer; + + llist_for_each_entry(peer, &sock->peers, list) { + if (!sockaddr_cmp(sa, (struct sockaddr *) &peer->remote)) + return peer; + } + return NULL; +} + +static struct octoi_peer * +alloc_peer(struct octoi_sock *sock, const struct sockaddr *sa, socklen_t sa_len) +{ + struct octoi_peer *peer = talloc_zero(sock, struct octoi_peer); + + if (!peer) + return NULL; + + OSMO_ASSERT(sa_len <= sizeof(peer->remote)); + memcpy(&peer->remote, sa, sa_len); + + peer->sock = sock; + llist_add_tail(&peer->list, &sock->peers); + + return peer; +} + +static int octoi_fd_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct octoi_sock *sock = ofd->data; + struct msgb *msg; + struct sockaddr_storage ss_remote; + socklen_t ss_remote_len = sizeof(ss_remote); + struct octoi_peer *peer; + int rc; + + if (what & OSMO_FD_WRITE) { + LOGP(DLINP, LOGL_INFO, "non-blocking connect succeeded\n"); + osmo_fd_write_disable(ofd); + } + + + if (what & OSMO_FD_READ) { + msg = msgb_alloc_c(sock, 2048, "OCTOI Rx"); + OSMO_ASSERT(msg); + rc = recvfrom(ofd->fd, msgb_data(msg), msgb_tailroom(msg), 0, + (struct sockaddr *) &ss_remote, &ss_remote_len); + if (rc <= 0) { + msgb_free(msg); + return -1; + } + msgb_put(msg, rc); + msg->l1h = msg->data; + if (msgb_l1len(msg) < sizeof(struct e1oip_hdr)) { + msgb_free(msg); + return -2; + } + msg->l2h = msg->l1h + sizeof(struct e1oip_hdr); + + /* look-up octoi_peer based on remote address */ + peer = find_peer_by_sockaddr(sock, (struct sockaddr *) &ss_remote); + if (!peer) { + peer = alloc_peer(sock, (struct sockaddr *) &ss_remote, ss_remote_len); + if (peer) { + osmo_sockaddr_str_from_sockaddr(&peer->cfg.remote, &ss_remote); + osmo_talloc_replace_string_fmt(peer, &peer->name, OSMO_SOCKADDR_STR_FMT, + OSMO_SOCKADDR_STR_FMT_ARGS(&peer->cfg.remote)); + LOGPEER(peer, LOGL_INFO, "peer created\n"); + } + } + OSMO_ASSERT(peer); + + /* dispatch received message to peer */ + rc = sock->rx_cb(peer, msg); + if (rc < 0) + return rc; + } + + return 0; +} + +void octoi_peer_destroy(struct octoi_peer *peer) +{ + if (!peer) + return; + + peer->tdm_permitted = false; + peer->sock = NULL; + e1oip_line_destroy(peer->iline); + + llist_del(&peer->list); + talloc_free(peer); +} + +static struct octoi_sock *octoi_sock_create(void *ctx) +{ + struct octoi_sock *sock = talloc_zero(ctx, struct octoi_sock); + + if (!sock) + return NULL; + + INIT_LLIST_HEAD(&sock->peers); + osmo_fd_setup(&sock->ofd, -1, OSMO_FD_READ, octoi_fd_cb, sock, 0); + + return sock; +} + +struct octoi_sock *octoi_sock_create_server(void *ctx, void *priv, const struct osmo_sockaddr_str *local) +{ + struct octoi_sock *sock = octoi_sock_create(ctx); + struct sockaddr_storage sa_local; + int rc; + + OSMO_ASSERT(sock); + + sock->priv = priv; + sock->cfg.server_mode = true; + sock->cfg.local = *local; + + /* bind to local addr/port; don't connect to any remote as we have many */ + osmo_sockaddr_str_to_sockaddr(&sock->cfg.local, &sa_local); + rc = osmo_sock_init_osa_ofd(&sock->ofd, SOCK_DGRAM, IPPROTO_UDP, + (struct osmo_sockaddr *) &sa_local, NULL, + OSMO_SOCK_F_BIND | OSMO_SOCK_F_NONBLOCK); + + if (rc < 0) { + LOGP(DLINP, LOGL_ERROR, "Unable to create OCTOI server socket\n"); + talloc_free(sock); + return NULL; + } + + LOGP(DLINP, LOGL_NOTICE, "OCTOI server socket at "OSMO_SOCKADDR_STR_FMT"\n", + OSMO_SOCKADDR_STR_FMT_ARGS(local)); + + return sock; +} + +struct octoi_sock *octoi_sock_create_client(void *ctx, void *priv, const struct osmo_sockaddr_str *local, + const struct osmo_sockaddr_str *remote) +{ + struct octoi_sock *sock = octoi_sock_create(ctx); + struct sockaddr_storage sa_remote; + struct octoi_peer *peer; + int rc; + + OSMO_ASSERT(sock); + + sock->priv = priv; + sock->cfg.server_mode = false; + if (local) + sock->cfg.local = *local; + + /* bind to local addr/port; don't connect to any remote as we have many */ + osmo_sockaddr_str_to_sockaddr(remote, &sa_remote); + if (local) { + struct sockaddr_storage sa_local; + osmo_sockaddr_str_to_sockaddr(&sock->cfg.local, &sa_local); + rc = osmo_sock_init_osa_ofd(&sock->ofd, SOCK_DGRAM, IPPROTO_UDP, + (struct osmo_sockaddr *) &sa_local, + (struct osmo_sockaddr *) &sa_remote, + OSMO_SOCK_F_CONNECT | OSMO_SOCK_F_BIND | OSMO_SOCK_F_NONBLOCK); + } else { + rc = osmo_sock_init_osa_ofd(&sock->ofd, SOCK_DGRAM, IPPROTO_UDP, + NULL, (struct osmo_sockaddr *) &sa_remote, + OSMO_SOCK_F_CONNECT | OSMO_SOCK_F_NONBLOCK); + } + if (rc < 0) { + LOGP(DLINP, LOGL_ERROR, "Unable to create OCTOI client socket\n"); + talloc_free(sock); + return NULL; + } + + LOGP(DLINP, LOGL_NOTICE, "OCTOI client socket to "OSMO_SOCKADDR_STR_FMT"\n", + OSMO_SOCKADDR_STR_FMT_ARGS(remote)); + + /* create [the only] peer */ + peer = alloc_peer(sock, (struct sockaddr *) &sa_remote, sizeof(sa_remote)); + peer->cfg.remote = *remote; + osmo_talloc_replace_string_fmt(peer, &peer->name, OSMO_SOCKADDR_STR_FMT, + OSMO_SOCKADDR_STR_FMT_ARGS(remote)); + + return sock; +} + +void octoi_sock_destroy(struct octoi_sock *sock) +{ + struct octoi_peer *p1, *p2; + + if (!sock) + return; + + llist_for_each_entry_safe(p1, p2, &sock->peers, list) { + OSMO_ASSERT(p1->sock == sock); + p1->sock = NULL; + /* FIXME: destroy FSM / priv */ + llist_del(&p1->list); + talloc_free(p1); + } + + osmo_fd_unregister(&sock->ofd); + close(sock->ofd.fd); + + LOGP(DLINP, LOGL_NOTICE, "OCTOI %s socket destroyed\n", + sock->cfg.server_mode ? "server" : "client"); + + talloc_free(sock); +} + +/* return the (only) peer of a octoi_sock client */ +struct octoi_peer *octoi_sock_client_get_peer(struct octoi_sock *sock) +{ + if (!sock) + return NULL; + OSMO_ASSERT(!sock->cfg.server_mode); + OSMO_ASSERT(llist_count(&sock->peers) == 1); + return llist_entry(sock->peers.next, struct octoi_peer, list); +} diff --git a/src/octoi/octoi_sock.h b/src/octoi/octoi_sock.h new file mode 100644 index 0000000..cf7b74b --- /dev/null +++ b/src/octoi/octoi_sock.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include + +#include + +#include +#include +#include +#include +#include + +/* Server side: + * - one socket, bound to configured port but not connected + * - multiple peers + * + * Client side: + * - one socket, [optionally locally bound] + connected to configured remote IP+port + * - single peer + */ + +#define LOGPEER(peer, lvl, fmt, args ...) \ + LOGP(DLINP, lvl, "%s: " fmt, (peer)->name, ## args) + +struct e1oip_line; +struct octoi_peer; + +struct octoi_sock { + struct llist_head list; /* member in global list */ + struct osmo_fd ofd; /* file descriptor */ + struct llist_head peers; /* list of peers */ + void *priv; + + int (*rx_cb)(struct octoi_peer *peer, struct msgb *msg); + + struct { + bool server_mode; + struct osmo_sockaddr_str local; /* local address */ + } cfg; +}; + +struct octoi_peer { + struct llist_head list; /* member in octoi_sock.peers */ + struct octoi_sock *sock; /* back-pointer to sock */ + struct sockaddr_storage remote; /* remote socket address */ + time_t last_rx_tdm; /* last time we received TDM from peer */ + struct e1oip_line *iline; + bool tdm_permitted; /* TDM messages are permitted (now) */ + char *name; /* human-readable name (just for logging) */ + void *priv; /* private data, e.g. fsm instance */ + + struct { + struct osmo_sockaddr_str remote; /* remote address */ + } cfg; +}; + +struct octoi_sock *octoi_sock_create_server(void *ctx, void *priv, + const struct osmo_sockaddr_str *local); + +struct octoi_sock *octoi_sock_create_client(void *ctx, void *priv, + const struct osmo_sockaddr_str *local, + const struct osmo_sockaddr_str *remote); + +void octoi_sock_destroy(struct octoi_sock *sock); + +struct octoi_peer *octoi_sock_client_get_peer(struct octoi_sock *sock); + +void octoi_peer_destroy(struct octoi_peer *peer); + +int octoi_tx(struct octoi_peer *peer, uint8_t msg_type, uint8_t flags, + const void *data, size_t len); + +int octoi_tx_echo_req(struct octoi_peer *peer, uint16_t seq_nr, const uint8_t *data, size_t data_len); + +int octoi_tx_echo_resp(struct octoi_peer *peer, uint16_t seq_nr, const uint8_t *data, size_t data_len); + +int octoi_tx_service_req(struct octoi_peer *peer, uint32_t service, const char *subscr_id, + const char *software_id, const char *software_version, + uint32_t capability_flags); + +int octoi_tx_redir_cmd(struct octoi_peer *peer, const char *server_ip, uint16_t server_port); + +int octoi_tx_auth_req(struct octoi_peer *peer, uint8_t rand_len, const uint8_t *rand, + uint8_t autn_len, const uint8_t *autn); + +int octoi_tx_auth_resp(struct octoi_peer *peer, uint8_t res_len, const uint8_t *res, + uint8_t auts_len, const uint8_t *auts); + +int octoi_tx_service_ack(struct octoi_peer *peer, uint32_t assigned_service, + const char *server_id, const char *software_id, + const char *software_version, uint32_t capability_flags); + +int octoi_tx_service_rej(struct octoi_peer *peer, uint32_t rejected_service, const char *message); + +int octoi_tx_error_ind(struct octoi_peer *peer, uint32_t cause, const char *message, + const uint8_t *orig, size_t orig_len); diff --git a/src/octoi/octoi_srv_fsm.c b/src/octoi/octoi_srv_fsm.c new file mode 100644 index 0000000..07dc1a7 --- /dev/null +++ b/src/octoi/octoi_srv_fsm.c @@ -0,0 +1,379 @@ +/* + * octoi_srv_fsm.c - OCTOI Server-side Finite State Machine + * + * (C) 2022 by Harald Welte + * + * 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 +#include +#include + +#include +#include + +#include "octoi.h" +#include "octoi_sock.h" +#include "octoi_fsm.h" +#include "e1oip.h" + +#define SUPPORTED_CAPABILITIES 0x00000000 + +enum octoi_server_fsm_state { + SRV_ST_INIT, /* just created [for new client] */ + SRV_ST_WAIT_AUTH_VEC, /* service request from client */ + SRV_ST_WAIT_AUTH_RESP, /* auth req sent, wait for auth resp */ + SRV_ST_ACCEPTED, /* service accepted */ + SRV_ST_REJECTED, /* service rejected */ + SRV_ST_REDIRECTED, /* service redirected */ +}; + +struct srv_state { + struct octoi_peer *peer; /* peer to which we belong */ + uint32_t service; /* service we are providing */ + uint32_t capability_flags; /* negotiated capabilities */ + struct { + char *subscriber_id; + char *software_id; + char *software_version; + uint32_t capability_flags; + } remote; + struct osmo_timer_list rx_alive_timer; + struct octoi_account *acc; + void *app_priv; /* application private data */ + const char *rej_str; +}; + +static void srv_st_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct srv_state *st = fi->priv; + struct octoi_server *srv = st->peer->priv; + struct octoi_account *acc; + struct msgb *msg = data; + struct e1oip_service_req *srv_req = NULL; + uint32_t service; + + switch (event) { + case OCTOI_SRV_EV_RX_SERVICE_REQ: + srv_req = (struct e1oip_service_req *) msgb_l2(msg); + service = ntohl(srv_req->requested_service); + LOGPFSML(fi, LOGL_INFO, "Rx SERVICE REQ (service=%u, subscriber='%s', " + "software='%s'/'%s', capabilities=0x%08x)\n", service, + srv_req->subscriber_id, srv_req->software_id, srv_req->software_version, + htonl(srv_req->capability_flags)); + if (service != E1OIP_SERVICE_E1_FRAMED) { + osmo_fsm_inst_state_chg(fi, SRV_ST_REJECTED, 0, 0); + octoi_tx_service_rej(st->peer, service, "Unsupported service"); + break; + } + /* fill peer structure with parameters received */ + st->service = service; + osmo_fsm_inst_update_id(fi, srv_req->subscriber_id); + osmo_talloc_replace_string(st->peer, &st->remote.subscriber_id, srv_req->subscriber_id); + osmo_talloc_replace_string(st->peer, &st->remote.software_id, srv_req->software_id); + osmo_talloc_replace_string(st->peer, &st->remote.software_version, srv_req->software_version); + st->remote.capability_flags = ntohl(srv_req->capability_flags); + /* intersect capabilities */ + st->capability_flags = st->remote.capability_flags & SUPPORTED_CAPABILITIES; + + /* TODO: later we would want to start looking up the subscriber in the HLR + * and request authentication tuples. */ + + /* check subscriber */ + acc = octoi_account_find(st->peer->sock->priv, st->remote.subscriber_id); + if (!acc) { + LOGPFSML(fi, LOGL_NOTICE, "Could not find user account %s, rejecting\n", + st->remote.subscriber_id); + st->rej_str = "Unknown user"; + goto reject; + } + st->acc = acc; + + switch (acc->mode) { + case ACCOUNT_MODE_ICE1USB: + case ACCOUNT_MODE_DAHDI: + /* check if a matching device exists for that account */ + st->app_priv = g_octoi->ops->client_connected(srv, st->peer, acc); + if (!st->app_priv) { + LOGPFSML(fi, LOGL_NOTICE, "Could not find E1 line for account %s, " + "rejecting\n", acc->user_id); + st->rej_str = "No line for user"; + goto reject; + } + osmo_talloc_replace_string(st->peer, &st->peer->name, acc->user_id); + osmo_fsm_inst_state_chg(fi, SRV_ST_ACCEPTED, 0, 0); + octoi_tx_service_ack(st->peer, st->service, "TODO-SRV", PACKAGE_NAME, + PACKAGE_VERSION, st->capability_flags); + break; + case ACCOUNT_MODE_REDIRECT: + octoi_tx_redir_cmd(st->peer, acc->u.redirect.to.ip, acc->u.redirect.to.port); + osmo_fsm_inst_state_chg(fi, SRV_ST_REDIRECTED, 10, 0); + break; + case ACCOUNT_MODE_NONE: + LOGPFSML(fi, LOGL_NOTICE, "User account %s has mode 'none', rejecting\n", + acc->user_id); + default: + st->rej_str = "Unsupported mode for user"; + goto reject; + break; + } + break; + default: + OSMO_ASSERT(0); + } + + return; + +reject: + octoi_tx_service_rej(st->peer, st->service, st->rej_str); + osmo_fsm_inst_state_chg(fi, SRV_ST_REJECTED, 10, 0); +} + +static void srv_st_wait_auth_vec(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case OCTOI_SRV_EV_RX_AUTH_VEC: + /* TODO */ + //octoi_tx_auth_req(peer, + break; + default: + OSMO_ASSERT(0); + } +} + +static void srv_st_wait_auth_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case OCTOI_SRV_EV_RX_AUTH_RESP: + /* TODO */ + default: + OSMO_ASSERT(0); + } +} + +static void srv_st_accepted_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct srv_state *st = fi->priv; + + st->peer->tdm_permitted = true; + osmo_timer_schedule(&st->rx_alive_timer, 3, 0); +} + +static void srv_st_accepted(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct srv_state *st = fi->priv; + + switch (event) { + case OCTOI_SRV_EV_RX_AUTH_RESP: /* Rx re-transmission from client side */ + /* re-transmit ack */ + octoi_tx_service_ack(st->peer, st->service, "TODO-SRV", PACKAGE_NAME, + PACKAGE_VERSION, st->capability_flags); + osmo_timer_schedule(&st->rx_alive_timer, 3, 0); + break; + case OCTOI_EV_RX_TDM_DATA: + e1oip_rcvmsg_tdm_data(st->peer->iline, data); + break; + default: + OSMO_ASSERT(0); + } +} + +static void srv_st_accepted_onleave(struct osmo_fsm_inst *fi, uint32_t next_state) +{ + struct srv_state *st = fi->priv; + + osmo_timer_del(&st->rx_alive_timer); + st->peer->tdm_permitted = false; +} + +static void srv_st_rejected(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct srv_state *st = fi->priv; + + switch (event) { + case OCTOI_SRV_EV_RX_SERVICE_REQ: + case OCTOI_SRV_EV_RX_AUTH_RESP: /* Rx re-transmission from client side */ + octoi_tx_service_rej(st->peer, st->service, st->rej_str); + break; + default: + OSMO_ASSERT(0); + } +} + +static void srv_st_redirected(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct srv_state *st = fi->priv; + + switch (event) { + case OCTOI_SRV_EV_RX_SERVICE_REQ: + case OCTOI_SRV_EV_RX_AUTH_RESP: /* Rx re-transmission from client side */ + octoi_tx_redir_cmd(st->peer, st->acc->u.redirect.to.ip, st->acc->u.redirect.to.port); + break; + default: + OSMO_ASSERT(0); + } +} + +static const struct osmo_fsm_state server_fsm_states[] = { + [SRV_ST_INIT] = { + .name = "INIT", + .in_event_mask = S(OCTOI_SRV_EV_RX_SERVICE_REQ), /* retransmit */ + .out_state_mask = S(SRV_ST_WAIT_AUTH_VEC) | S(SRV_ST_ACCEPTED) | S(SRV_ST_REJECTED), + .action = srv_st_init, + }, + [SRV_ST_WAIT_AUTH_VEC] = { + .name = "WAIT_AUTH_VEC", + .in_event_mask = S(OCTOI_SRV_EV_RX_AUTH_VEC), + .out_state_mask = S(SRV_ST_WAIT_AUTH_RESP) | S(SRV_ST_REJECTED), + .action = srv_st_wait_auth_vec, + }, + [SRV_ST_WAIT_AUTH_RESP] = { + .name = "WAIT_AUTH_RESP", + .in_event_mask = S(OCTOI_SRV_EV_RX_AUTH_RESP), + .out_state_mask = S(SRV_ST_ACCEPTED) | S(SRV_ST_REJECTED) | S(SRV_ST_REDIRECTED), + .action = srv_st_wait_auth_resp, + }, + [SRV_ST_ACCEPTED] = { + .name = "ACCEPTED", + .in_event_mask = S(OCTOI_SRV_EV_RX_AUTH_RESP) | /* retransmit */ + S(OCTOI_EV_RX_TDM_DATA), + .action = srv_st_accepted, + .onenter = srv_st_accepted_onenter, + .onleave = srv_st_accepted_onleave, + }, + [SRV_ST_REJECTED] = { + .name = "REJECTED", + .in_event_mask = S(OCTOI_SRV_EV_RX_SERVICE_REQ) | /* retransmit */ + S(OCTOI_SRV_EV_RX_AUTH_RESP), /* retransmit */ + .action = srv_st_rejected, + }, + [SRV_ST_REDIRECTED] = { + .name = "REDIRECTED", + .in_event_mask = S(OCTOI_SRV_EV_RX_SERVICE_REQ) | /* retransmit */ + S(OCTOI_SRV_EV_RX_AUTH_RESP), /* retransmit */ + .action = srv_st_redirected, + }, +}; + +static void srv_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct srv_state *st = fi->priv; + struct msgb *msg = data; + struct e1oip_echo *echo_req; + struct e1oip_error_ind *err_ind; + + switch (event) { + case OCTOI_EV_RX_ECHO_REQ: + echo_req = msgb_l2(msg); + octoi_tx_echo_resp(st->peer, ntohs(echo_req->seq_nr), echo_req->data, msgb_l2len(msg)); + break; + case OCTOI_EV_RX_ECHO_RESP: + /* TODO: update state, peer has responded! */ + break; + case OCTOI_EV_RX_ERROR_IND: + err_ind = msgb_l2(msg); + LOGPFSML(fi, LOGL_ERROR, "Rx OCTOI ERROR IND (cause=0x%08x, msg=%s)\n", + ntohl(err_ind->cause), err_ind->error_message); + break; + default: + OSMO_ASSERT(0); + } +} + +static int srv_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + switch (fi->state) { + case SRV_ST_REJECTED: + case SRV_ST_REDIRECTED: + /* 10s timeout has expired, we can now forget about this peer */ + /* request termination */ + return 1; + } + + return 0; +} + +static void srv_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct srv_state *st = fi->priv; + + osmo_timer_del(&st->rx_alive_timer); + + /* as long as 'fi' lives within 'peer' we cannot recursively destroy peer */ + talloc_steal(OTC_SELECT, fi); + + if (g_octoi->ops->peer_disconnected) + g_octoi->ops->peer_disconnected(st->peer); + + octoi_peer_destroy(st->peer); +} + +struct osmo_fsm octoi_server_fsm = { + .name = "OCTOI_SERVER", + .states = server_fsm_states, + .num_states = ARRAY_SIZE(server_fsm_states), + .allstate_event_mask = S(OCTOI_EV_RX_ECHO_REQ) | + S(OCTOI_EV_RX_ECHO_RESP) | + S(OCTOI_EV_RX_ERROR_IND), + .allstate_action = srv_allstate_action, + .timer_cb = srv_fsm_timer_cb, + .log_subsys = DLINP, + .event_names = octoi_fsm_event_names, + .cleanup = srv_fsm_cleanup, +}; + + +static void srv_rx_alive_timer_cb(void *data) +{ + struct osmo_fsm_inst *fi = data; + struct srv_state *st = fi->priv; + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + + if (ts.tv_sec - st->peer->last_rx_tdm > 3) { + LOGPFSML(fi, LOGL_NOTICE, "No TDM data received for >= 3 seconds, declaring peer dead\n"); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_TIMEOUT, NULL); + } else + osmo_timer_schedule(&st->rx_alive_timer, 3, 0); +} + +/* call-back function for every received OCTOI socket message for given peer */ +int octoi_srv_fsm_rx_cb(struct octoi_peer *peer, struct msgb *msg) +{ + + /* ensure peer->priv points to a fsm_inst */ + if (!peer->priv) { + struct osmo_fsm_inst *fi; + struct srv_state *st; + + fi = osmo_fsm_inst_alloc(&octoi_server_fsm, peer, NULL, LOGL_DEBUG, NULL); + OSMO_ASSERT(fi); + + st = talloc_zero(fi, struct srv_state); + OSMO_ASSERT(st); + st->peer = peer; + osmo_timer_setup(&st->rx_alive_timer, srv_rx_alive_timer_cb, fi); + fi->priv = st; + + peer->priv = fi; + } + OSMO_ASSERT(peer->priv); + if (!peer->iline) + peer->iline = e1oip_line_alloc(peer); + + return _octoi_fsm_rx_cb(peer, msg); +} diff --git a/src/octoi/octoi_srv_vty.c b/src/octoi/octoi_srv_vty.c new file mode 100644 index 0000000..e5b91c7 --- /dev/null +++ b/src/octoi/octoi_srv_vty.c @@ -0,0 +1,386 @@ +/* + * octoi_srv_vty.c - VTY interface for OCTOI server side + * + * (C) 2022 by Harald Welte + * + * 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 +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "octoi.h" +#include "octoi_sock.h" +#include "octoi_fsm.h" +#include "octoi_vty.h" + +/*********************************************************************** + * core data structures + ***********************************************************************/ + +const struct value_string octoi_account_mode_name[] = { + { ACCOUNT_MODE_NONE, "none" }, + { ACCOUNT_MODE_ICE1USB, "ice1usb" }, + { ACCOUNT_MODE_REDIRECT, "redirect" }, + { ACCOUNT_MODE_DAHDI, "dahdi" }, + { 0, NULL } +}; + +static struct octoi_account *_account_create(void *ctx, const char *user_id) +{ + struct octoi_account *ac = talloc_zero(ctx, struct octoi_account); + if (!ac) + return NULL; + + ac->user_id = talloc_strdup(ac, user_id); + if (!ac->user_id) { + talloc_free(ac); + return NULL; + } + + return ac; +} + +static struct octoi_account *octoi_server_account_create(struct octoi_server *srv, const char *user_id) +{ + struct octoi_account *ac = _account_create(srv, user_id); + if (!ac) + return NULL; + + llist_add_tail(&ac->list, &srv->cfg.accounts); + + return ac; +} + +struct octoi_account *octoi_client_account_create(struct octoi_client *clnt, const char *user_id) +{ + + struct octoi_account *ac = _account_create(clnt, user_id); + if (!ac) + return NULL; + + OSMO_ASSERT(!clnt->cfg.account); + clnt->cfg.account = ac; + + ac->mode = ACCOUNT_MODE_ICE1USB; + + return ac; +} + +struct octoi_account *octoi_account_find(struct octoi_server *srv, const char *user_id) +{ + struct octoi_account *ac; + + llist_for_each_entry(ac, &srv->cfg.accounts, list) { + if (!strcmp(ac->user_id, user_id)) + return ac; + } + return NULL; +} + +static struct octoi_server *octoi_server_alloc(void *ctx) +{ + struct octoi_server *srv = talloc_zero(ctx, struct octoi_server); + if (!srv) + return NULL; + + INIT_LLIST_HEAD(&srv->cfg.accounts); + + return srv; +} + +/*********************************************************************** + * VTY + ***********************************************************************/ + +static struct cmd_node srv_node = { + (enum node_type) OCTOI_SRV_NODE, + "%s(config-octoi-server)# ", + 1, +}; + +static struct cmd_node account_node = { + (enum node_type) OCTOI_ACCOUNT_NODE, + "%s(config-octoi-server-account)# ", + 1, +}; + +DEFUN(cfg_server, cfg_server_cmd, + "octoi-server", + "Configure the OCTOI server\n") +{ + struct octoi_server *srv = g_octoi->server; + + if (!srv) + srv = g_octoi->server = octoi_server_alloc(g_octoi); + OSMO_ASSERT(srv); + + vty->node = OCTOI_SRV_NODE; + vty->index = srv; + + return CMD_SUCCESS; +} + +#if 0 +DEFUN(cfg_no_server, cfg_no_server_cmd, + "no octoi-server", + NO_STR "Disable OCTOI server\n") +{ + /* we'd need to iterate over all accounts and terminate any + * octoi_server_fsm that might exist for each account */ +} +#endif + +DEFUN(cfg_srv_local, cfg_srv_local_cmd, + "local-bind (A.B.C.D|X:X::X:X) <0-65535>", + "Local OCTOI socket bind address/port\n" + "Local OCTOI IPv4 Address\n" + "Local OCTOI IPv6 Address\n" + "Local OCTOI UDP Port Number\n") +{ + struct octoi_server *srv = vty->index; + int rc; + + rc = osmo_sockaddr_str_from_str(&srv->cfg.local, argv[0], atoi(argv[1])); + if (rc < 0) { + vty_out(vty, "%% sockaddr Error: %s%s", strerror(errno), VTY_NEWLINE); + return CMD_WARNING; + } + + if (srv->sock) + octoi_sock_destroy(srv->sock); + + srv->sock = octoi_sock_create_server(srv, srv, &srv->cfg.local); + if (!srv->sock) { + vty_out(vty, "%% failed to create/bind socket: %s%s", strerror(errno), VTY_NEWLINE); + return CMD_WARNING; + } + srv->sock->rx_cb = octoi_srv_fsm_rx_cb; + + return CMD_SUCCESS; +} + +DEFUN(cfg_srv_account, cfg_srv_account_cmd, + "account USER_ID", + "Configure a local user account\n" + "User ID\n") +{ + struct octoi_server *srv = vty->index; + const char *user_id = argv[0]; + struct octoi_account *ac = octoi_account_find(srv, user_id); + if (!ac) + ac = octoi_server_account_create(srv, user_id); + if (!ac) + return CMD_WARNING; + + vty->node = OCTOI_ACCOUNT_NODE; + vty->index = ac; + + return CMD_SUCCESS; +} + +#if 0 +DEFUN(cfg_srv_no_account, cfg_serv_no_account_cmd, + "no account USER_ID", + NO_STR "Remove a local user account\n" + "User ID\n") +{ + struct octoi_server *srv = vty->index; + const char *user_id = argv[0]; + struct octoi_account *ac = octoi_account_find(srv, user_id); + if (!ac) + return CMD_WARNING; + + /* we'd need to iterate all octoi_server_fsm instances and terminate any + * pointing to this account */ +} +#endif + +gDEFUN(cfg_account_mode, cfg_account_mode_cmd, + "mode (ice1usb|redirect)", + "Operational mode of account\n" + "Connect to local icE1usb (identified by USB serial + line number)\n" + "Redirect to other IP/Port\n") +{ + struct octoi_account *acc = vty->index; + + /* leave old mode */ + switch (acc->mode) { + case ACCOUNT_MODE_ICE1USB: + talloc_free(acc->u.ice1usb.usb_serial); + break; + default: + break; + } + memset(&acc->u, 0, sizeof(acc->u)); + + if (!strcmp(argv[0], "ice1usb")) { + acc->mode = ACCOUNT_MODE_ICE1USB; + } else if (!strcmp(argv[0], "redirect")) { + acc->mode = ACCOUNT_MODE_REDIRECT; + } else + OSMO_ASSERT(0); + + return CMD_SUCCESS; +} + +#define ICE1_STR "icE1usb settings\n" + +gDEFUN(cfg_account_ice1_serno, cfg_account_ice1_serno_cmd, + "ice1usb serial-number SERNO", + ICE1_STR "USB Serial Number String\n" "USB Serial Number String\n") +{ + struct octoi_account *acc = vty->index; + + if (acc->mode != ACCOUNT_MODE_ICE1USB) { + vty_out(vty, "%% Error: Not in icE1usb mode!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + osmo_talloc_replace_string(acc, &acc->u.ice1usb.usb_serial, argv[0]); + return CMD_SUCCESS; +} + +gDEFUN(cfg_account_ice1_line, cfg_account_ice1_line_cmd, + "ice1usb line-number <0-1>", + ICE1_STR "E1 Line number\n" "E1 Line number\n") +{ + struct octoi_account *acc = vty->index; + + if (acc->mode != ACCOUNT_MODE_ICE1USB) { + vty_out(vty, "%% Error: Not in icE1usb mode!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + acc->u.ice1usb.line_nr = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_account_redir, cfg_account_redir_cmd, + "redirect (A.B.C.D|X:X::X:X) <0-65535>", + "Redirect to other IP/Port\n" + "Destination IPv4 address\n" + "Destination IPv6 address\n" + "Destination UDP port\n") +{ + struct octoi_account *acc = vty->index; + int rc; + + if (acc->mode != ACCOUNT_MODE_REDIRECT) { + vty_out(vty, "%% Error: Not in redirect mode!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + rc = osmo_sockaddr_str_from_str(&acc->u.redirect.to, argv[0], atoi(argv[1])); + if (rc < 0) { + vty_out(vty, "%% sockaddr Error: %s%s", strerror(errno), VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +void octoi_vty_write_one_account(struct vty *vty, const struct octoi_account *acc) +{ + if (!acc) + return; + + vty_out(vty, " account %s%s", acc->user_id, VTY_NEWLINE); + vty_out(vty, " mode %s%s", get_value_string(octoi_account_mode_name, acc->mode), + VTY_NEWLINE); + + switch (acc->mode) { + case ACCOUNT_MODE_NONE: + break; + case ACCOUNT_MODE_ICE1USB: + if (acc->u.ice1usb.usb_serial) + vty_out(vty, " ice1usb serial-number %s%s", acc->u.ice1usb.usb_serial, + VTY_NEWLINE); + + vty_out(vty, " ice1usb line-number %u%s", acc->u.ice1usb.line_nr, VTY_NEWLINE); + break; + case ACCOUNT_MODE_REDIRECT: + vty_out(vty, " redirect %s %u%s", acc->u.redirect.to.ip, acc->u.redirect.to.port, + VTY_NEWLINE); + break; + case ACCOUNT_MODE_DAHDI: + OSMO_ASSERT(0); + break; + } +} + +static int config_write_octoi_srv(struct vty *vty) +{ + struct octoi_account *acc; + struct octoi_server *srv = g_octoi->server; + + if (!srv) + return 0; + + vty_out(vty, "octoi-server%s", VTY_NEWLINE); + if (strlen(srv->cfg.local.ip)) { + vty_out(vty, " local-bind %s %u%s", srv->cfg.local.ip, srv->cfg.local.port, + VTY_NEWLINE); + } + + llist_for_each_entry(acc, &srv->cfg.accounts, list) + octoi_vty_write_one_account(vty, acc); + + return 0; +} + +DEFUN(show_server, show_server_cmd, + "show octoi-server", + SHOW_STR "Display information about the OCTOI Server\n") +{ + struct octoi_server *srv = g_octoi->server; + struct octoi_sock *sock = srv->sock; + + vty_show_octoi_sock(vty, sock); + + return CMD_SUCCESS; +} + +void octoi_server_vty_init(void) +{ + + install_element_ve(&show_server_cmd); + + install_node(&account_node, NULL); + install_element(OCTOI_ACCOUNT_NODE, &cfg_account_mode_cmd); + install_element(OCTOI_ACCOUNT_NODE, &cfg_account_ice1_serno_cmd); + install_element(OCTOI_ACCOUNT_NODE, &cfg_account_ice1_line_cmd); + install_element(OCTOI_ACCOUNT_NODE, &cfg_account_redir_cmd); + + install_node(&srv_node, config_write_octoi_srv); + install_element(CONFIG_NODE, &cfg_server_cmd); + //install_element(CONFIG_NODE, &cfg_no_server_cmd); + install_element(OCTOI_SRV_NODE, &cfg_srv_local_cmd); + install_element(OCTOI_SRV_NODE, &cfg_srv_account_cmd); + //install_element(CONFIG_SRV_NODE, &cfg_srv_no_account_cmd); +} diff --git a/src/octoi/octoi_vty.h b/src/octoi/octoi_vty.h new file mode 100644 index 0000000..6f18802 --- /dev/null +++ b/src/octoi/octoi_vty.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include "octoi.h" + +extern struct cmd_element cfg_account_mode_cmd; +extern struct cmd_element cfg_account_ice1_serno_cmd; +extern struct cmd_element cfg_account_ice1_line_cmd; + +struct octoi_account *octoi_client_account_create(struct octoi_client *clnt, const char *user_id); + +void octoi_vty_write_one_account(struct vty *vty, const struct octoi_account *acc); + +void vty_show_octoi_sock(struct vty *vty, struct octoi_sock *sock); diff --git a/src/osmo-e1d.c b/src/osmo-e1d.c index f86a92a..1e18f88 100644 --- a/src/osmo-e1d.c +++ b/src/osmo-e1d.c @@ -39,11 +39,14 @@ #include #include #include +#include +#include #include #include #include "e1d.h" +#include #include "usb.h" #include "log.h" @@ -83,6 +86,7 @@ static void sig_handler(int signo) static struct vty_app_info vty_info = { .name = "osmo-e1d", .version = PACKAGE_VERSION, + .go_parent_cb = e1d_vty_go_parent, .copyright = "(C) 2019-2022 by Sylvain Munaut, Harald Welte and contributors\r\n", "License GPLv2+: GNU GPL version 2 or later \r\n" @@ -176,7 +180,12 @@ int main(int argc, char *argv[]) vty_init(&vty_info); logging_vty_add_cmds(); e1d_vty_init(e1d); + octoi_init(g_e1d_ctx, e1d, &e1d_octoi_ops); rate_ctr_init(e1d); + osmo_stat_item_init(e1d); + osmo_stats_vty_add_cmds(); + osmo_talloc_vty_add_cmds(); + osmo_fsm_vty_add_cmds(); handle_options(argc, argv); diff --git a/src/usb.c b/src/usb.c index 40518d8..1949485 100644 --- a/src/usb.c +++ b/src/usb.c @@ -939,6 +939,8 @@ _e1_usb_open_device(struct e1_daemon *e1d, struct libusb_device *dev) if (line_data->ep_int) resubmit_irq(line); + e1_line_active(line); + next_interface: line_nr++; } diff --git a/src/vty.c b/src/vty.c index bdd0457..ba6b1ac 100644 --- a/src/vty.c +++ b/src/vty.c @@ -24,6 +24,7 @@ #include #include #include +#include #include @@ -36,7 +37,9 @@ #include #include +#include #include + #include "e1d.h" #include "usb.h" @@ -60,6 +63,23 @@ static struct cmd_node line_node = { 1, }; +int e1d_vty_go_parent(struct vty *vty) +{ + struct e1_line *line; + + switch (vty->node) { + case LINE_NODE: + line = vty->index; + vty->node = INTF_NODE; + vty->index = line->intf; + break; + default: + return octoi_vty_go_parent(vty); + } + + return 0; +} + #if 0 static void vty_dump_ts(struct vty *vty, const struct e1_ts *ts) { @@ -135,6 +155,7 @@ const struct value_string e1_ts_mode_names[] = { const struct value_string e1_line_mode_names[] = { { E1_LINE_MODE_CHANNELIZED, "channelized" }, { E1_LINE_MODE_SUPERCHANNEL, "superchannel" }, + { E1_LINE_MODE_E1OIP, "e1oip" }, { 0, NULL } }; @@ -294,14 +315,17 @@ DEFUN(cfg_e1d_if_line, cfg_e1d_if_line_cmd, "line <0-255>", } DEFUN(cfg_e1d_if_line_mode, cfg_e1d_if_line_mode_cmd, - "mode (channelized|superchannel)", + "mode (channelized|superchannel|e1oip)", "Configure the mode of the E1 line\n" "Channelized (64kBps timeslot) mode\n" "Superchannel (1xHDLC over 31x64kBps) mode\n") { struct e1_line *line = vty->index; - - line->mode = get_string_value(e1_line_mode_names, argv[0]); + enum e1_line_mode new_mode = get_string_value(e1_line_mode_names, argv[0]); + if (line->mode != new_mode) { + /* FIXME: clean up any old state */ + line->mode = new_mode; + } return CMD_SUCCESS; }