From beb10ef02a10d73537a97f6f21aad36664c9b266 Mon Sep 17 00:00:00 2001 From: Alexander Couzens Date: Tue, 1 Nov 2016 22:05:13 +0100 Subject: [PATCH] add basic unixsocket support Allow to connect to a unix socket for communicating with LAPD. Change-Id: Ia5723b09a5c68a0505829dc732def981e60a907a --- include/Makefile.am | 3 +- include/osmocom/abis/e1_input.h | 4 + include/osmocom/abis/unixsocket_proto.h | 31 +++ src/Makefile.am | 3 +- src/e1_input.c | 2 + src/e1_input_vty.c | 25 +- src/input/unixsocket.c | 347 ++++++++++++++++++++++++ 7 files changed, 411 insertions(+), 4 deletions(-) create mode 100644 include/osmocom/abis/unixsocket_proto.h create mode 100644 src/input/unixsocket.c diff --git a/include/Makefile.am b/include/Makefile.am index 16fa506..2048520 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -3,4 +3,5 @@ noinst_HEADERS=mISDNif.h internal.h nobase_include_HEADERS = osmocom/abis/ipa.h osmocom/abis/trau_frame.h \ osmocom/abis/ipa_proxy.h osmocom/abis/ipaccess.h osmocom/abis/abis.h \ osmocom/abis/subchan_demux.h osmocom/abis/e1_input.h \ - osmocom/abis/lapd.h osmocom/abis/lapd_pcap.h osmocom/trau/osmo_ortp.h + osmocom/abis/lapd.h osmocom/abis/lapd_pcap.h osmocom/trau/osmo_ortp.h \ + osmocom/abis/unixsocket_proto.h diff --git a/include/osmocom/abis/e1_input.h b/include/osmocom/abis/e1_input.h index 4c7f8a0..8501d5c 100644 --- a/include/osmocom/abis/e1_input.h +++ b/include/osmocom/abis/e1_input.h @@ -183,6 +183,7 @@ struct e1inp_line { unsigned int num; const char *name; unsigned int port_nr; + char *sock_path; struct rate_ctr_group *rate_ctr; /* keepalive configuration */ @@ -303,6 +304,9 @@ int e1inp_vty_init(void); struct gsm_network; int ipaccess_setup(struct gsm_network *gsmnet); +/* activate superchannel or deactive to use timeslots. only valid for unixsocket driver */ +void e1inp_ericsson_set_altc(struct e1inp_line *unixlinue, int superchannel); + extern struct llist_head e1inp_driver_list; extern struct llist_head e1inp_line_list; diff --git a/include/osmocom/abis/unixsocket_proto.h b/include/osmocom/abis/unixsocket_proto.h new file mode 100644 index 0000000..25718ff --- /dev/null +++ b/include/osmocom/abis/unixsocket_proto.h @@ -0,0 +1,31 @@ + +#ifndef UNIXSOCKET_PROTO_H +#define UNIXSOCKET_PROTO_H + +/* The unix socket protocol is using a 2 byte header + * containg the version and type. + * + * header: | 1b version | 1b type | + * + * for data packets it would be + * + * data: | 0x1 | 0x0 | lapd ..| + * control: | 0x1 | 0x1 | control payload | + * + * Atm there is only one control packet: + * - set_altc (superchannel or timeslot) + * + * set_altc payload: + * | 4b magic | 1b new_state| + * | 0x23004200 | 0x0 | to timeslot + * | 0x23004200 | 0x1 | to superchannel + */ + +#define UNIXSOCKET_PROTO_VERSION 0x1 + +enum { + UNIXSOCKET_PROTO_DATA = 0x0, + UNIXSOCKET_PROTO_CONTROL = 0x1, +}; + +#endif /* UNIXSOCKET_PROTO_H */ diff --git a/src/Makefile.am b/src/Makefile.am index b24f2cf..760c1f5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -24,7 +24,8 @@ libosmoabis_la_SOURCES = init.c \ input/lapd.c \ input/lapd_pcap.c \ input/misdn.c \ - input/rs232.c + input/rs232.c \ + input/unixsocket.c libosmotrau_la_CFLAGS = $(AM_CFLAGS) $(ORTP_CFLAGS) libosmotrau_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(TRAU_LIBVERSION) diff --git a/src/e1_input.c b/src/e1_input.c index 09fea59..1e1252e 100644 --- a/src/e1_input.c +++ b/src/e1_input.c @@ -807,6 +807,7 @@ void e1inp_misdn_init(void); void e1inp_dahdi_init(void); void e1inp_ipaccess_init(void); void e1inp_rs232_init(void); +void e1inp_unixsocket_init(void); void e1inp_init(void) { @@ -821,4 +822,5 @@ void e1inp_init(void) #endif e1inp_ipaccess_init(); e1inp_rs232_init(); + e1inp_unixsocket_init(); } diff --git a/src/e1_input_vty.c b/src/e1_input_vty.c index 5320bb3..9d69586 100644 --- a/src/e1_input_vty.c +++ b/src/e1_input_vty.c @@ -38,12 +38,13 @@ /* CONFIG */ -#define E1_DRIVER_NAMES "(misdn|misdn_lapd|dahdi|ipa)" +#define E1_DRIVER_NAMES "(misdn|misdn_lapd|dahdi|ipa|unixsocket)" #define E1_DRIVER_HELP "mISDN supported E1 Card (kernel LAPD)\n" \ "mISDN supported E1 Card (userspace LAPD)\n" \ "DAHDI supported E1/T1/J1 Card\n" \ "IPA TCP/IP input\n" \ - "HSL TCP/IP input" + "HSL TCP/IP input\n" \ + "Unix socket input\n" #define E1_LINE_HELP "Configure E1/T1/J1 Line\n" "Line Number\n" @@ -88,6 +89,25 @@ DEFUN(cfg_e1line_port, cfg_e1_line_port_cmd, return CMD_SUCCESS; } +DEFUN(cfg_e1line_socket, cfg_e1_line_socket_cmd, + "e1_line <0-255> socket .SOCKET", + E1_LINE_HELP "Set socket path for unixsocket\n" + "socket path\n") +{ + struct e1inp_line *line; + int e1_nr = atoi(argv[0]); + + line = e1inp_line_find(e1_nr); + if (!line) { + vty_out(vty, "%% Line %d doesn't exist%s", e1_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + line->sock_path = talloc_strdup(line, argv[1]); + + return CMD_SUCCESS; +} + #define KEEPALIVE_HELP "Enable keep-alive probing\n" static int set_keepalive_params(struct vty *vty, int e1_nr, int idle, int num_probes, int probe_interval) @@ -363,6 +383,7 @@ int e1inp_vty_init(void) vty_install_default(L_E1INP_NODE); install_element(L_E1INP_NODE, &cfg_e1_line_driver_cmd); install_element(L_E1INP_NODE, &cfg_e1_line_port_cmd); + install_element(L_E1INP_NODE, &cfg_e1_line_socket_cmd); install_element(L_E1INP_NODE, &cfg_e1_line_name_cmd); install_element(L_E1INP_NODE, &cfg_e1_line_keepalive_cmd); install_element(L_E1INP_NODE, &cfg_e1_line_keepalive_params_cmd); diff --git a/src/input/unixsocket.c b/src/input/unixsocket.c new file mode 100644 index 0000000..4f287ae --- /dev/null +++ b/src/input/unixsocket.c @@ -0,0 +1,347 @@ +/* OpenBSC Abis receive lapd over a unix socket */ + +/* (C) 2016 by sysmocom s.f.m.c. GmbH + * + * Author: Alexander Couzens + * Based on other e1_input drivers. + * + * All Rights Reserved + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include "internal.h" + +void *tall_unixsocket_ctx; +#define UNIXSOCKET_ALLOC_SIZE 1600 +#define UNIXSOCKET_SOCK_PATH_DEFAULT "/tmp/osmo_abis_line_" + +struct unixsocket_line { + struct osmo_fd fd; +}; + +static int unixsocket_line_update(struct e1inp_line *line); +static int ts_want_write(struct e1inp_ts *e1i_ts); + +static int unixsocket_exception_cb(struct osmo_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + + LOGP(DLINP, LOGL_ERROR, + "Socket connection failure, reconnecting... (line=%p, fd=%d)\n", + line, bfd->fd); + + /* Unregister faulty file descriptor from select loop */ + if(osmo_fd_is_registered(bfd)) { + LOGP(DLINP, LOGL_DEBUG, + "removing inactive socket from select loop... (line=%p, fd=%d)\n", + line, bfd->fd); + osmo_fd_unregister(bfd); + } + + /* Close faulty file descriptor */ + close(bfd->fd); + + unixsocket_line_update(line); + + return 0; +} + +static int unixsocket_read_cb(struct osmo_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + struct msgb *msg = msgb_alloc(UNIXSOCKET_ALLOC_SIZE, "UNIXSOCKET TS"); + uint8_t version; + uint8_t controldata; + int ret; + + if (!msg) + return -ENOMEM; + + ret = read(bfd->fd, msg->data, UNIXSOCKET_ALLOC_SIZE - 16); + if (ret == 0) { + unixsocket_exception_cb(bfd); + goto fail; + } else if (ret < 0) { + perror("read "); + goto fail; + } else if (ret < 2) { + /* packet must be at least 2 byte long to hold version + control/data header */ + LOGP(DLMI, LOGL_ERROR, "received to small packet: %d < 2", ret); + ret = -1; + goto fail; + } + msgb_put(msg, ret); + + LOGP(DLMI, LOGL_DEBUG, "rx msg: %s (fd=%d)\n", + osmo_hexdump_nospc(msg->data, msg->len), bfd->fd); + + /* check version header */ + version = msgb_pull_u8(msg); + controldata = msgb_pull_u8(msg); + + if (version != UNIXSOCKET_PROTO_VERSION) { + LOGP(DLMI, LOGL_ERROR, "received message with invalid version %d. valid: %d", + ret, UNIXSOCKET_PROTO_VERSION); + ret = -1; + goto fail; + } + + switch (controldata) { + case UNIXSOCKET_PROTO_DATA: + return e1inp_rx_ts_lapd(&line->ts[0], msg); + case UNIXSOCKET_PROTO_CONTROL: + LOGP(DLMI, LOGL_ERROR, "received (invalid) control message."); + ret = -1; + break; + default: + LOGP(DLMI, LOGL_ERROR, "received invalid message."); + ret = -1; + break; + } +fail: + msgb_free(msg); + return ret; +} + +static void timeout_ts1_write(void *data) +{ + struct e1inp_ts *e1i_ts = (struct e1inp_ts *)data; + + /* trigger write of ts1, due to tx delay timer */ + ts_want_write(e1i_ts); +} + +static int unixsocket_write_cb(struct osmo_fd *bfd) +{ + struct e1inp_line *line = bfd->data; + struct e1inp_ts *e1i_ts = &line->ts[0]; + struct msgb *msg; + struct e1inp_sign_link *sign_link; + + bfd->when &= ~BSC_FD_WRITE; + + /* get the next msg for this timeslot */ + msg = e1inp_tx_ts(e1i_ts, &sign_link); + if (!msg) { + /* no message after tx delay timer */ + LOGP(DLINP, LOGL_INFO, + "no message available (line=%p)\n", line); + return 0; + } + + /* set tx delay timer for next event */ + e1i_ts->sign.tx_timer.cb = timeout_ts1_write; + e1i_ts->sign.tx_timer.data = e1i_ts; + + osmo_timer_schedule(&e1i_ts->sign.tx_timer, 0, e1i_ts->sign.delay); + + LOGP(DLINP, LOGL_DEBUG, "sending: %s (line=%p)\n", + msgb_hexdump(msg), line); + lapd_transmit(e1i_ts->lapd, sign_link->tei, + sign_link->sapi, msg); + + return 0; +} + +static int unixsocket_cb(struct osmo_fd *bfd, unsigned int what) +{ + int ret = 0; + + if (what & BSC_FD_READ) + ret = unixsocket_read_cb(bfd); + if (what & BSC_FD_WRITE) + ret = unixsocket_write_cb(bfd); + + return ret; +} + +static int ts_want_write(struct e1inp_ts *e1i_ts) +{ + struct unixsocket_line *line = e1i_ts->line->driver_data; + + line->fd.when |= BSC_FD_WRITE; + + return 0; +} + +static void unixsocket_write_msg(struct msgb *msg, struct osmo_fd *bfd) { + int ret; + + LOGP(DLMI, LOGL_DEBUG, "tx msg: %s (fd=%d)\n", + osmo_hexdump_nospc(msg->data, msg->len), bfd->fd); + + ret = write(bfd->fd, msg->data, msg->len); + msgb_free(msg); + if (ret == -1) + unixsocket_exception_cb(bfd); + else if (ret < 0) + LOGP(DLMI, LOGL_NOTICE, "%s write failed %d\n", __func__, ret); +} + +/*! + * \brief unixsocket_write_msg lapd callback for data to unixsocket + * \param msg + * \param cbdata + */ +static void unixsocket_write_msg_lapd_cb(struct msgb *msg, void *cbdata) +{ + struct osmo_fd *bfd = cbdata; + + /* data|control */ + msgb_push_u8(msg, UNIXSOCKET_PROTO_DATA); + /* add version header */ + msgb_push_u8(msg, UNIXSOCKET_PROTO_VERSION); + + unixsocket_write_msg(msg, bfd); +} + +static int unixsocket_line_update(struct e1inp_line *line) +{ + struct unixsocket_line *config; + char sock_path[PATH_MAX]; + int ret = 0; + int i; + + if (line->sock_path) + strcpy(sock_path, line->sock_path); + else + sprintf(sock_path, "%s%d", UNIXSOCKET_SOCK_PATH_DEFAULT, + line->num); + + LOGP(DLINP, LOGL_NOTICE, "line update (line=%p)\n", line); + + if (!line->driver_data) + line->driver_data = talloc_zero(line, struct unixsocket_line); + + if (!line->driver_data) { + LOGP(DLINP, LOGL_ERROR, + "OOM in line update (line=%p)\n", line); + return -ENOMEM; + } + + config = line->driver_data; + config->fd.data = line; + config->fd.when = BSC_FD_READ; + config->fd.cb = unixsocket_cb; + + /* Open unix domain socket */ + ret = osmo_sock_unix_init(SOCK_SEQPACKET, 0, sock_path, + OSMO_SOCK_F_CONNECT); + if (ret < 0) { + /* Note: We will not free the allocated driver_data memory if + * opening the socket fails. The caller may want to call this + * function multiple times using config->fd.data as line + * parameter. Freeing now would destroy that reference. */ + LOGP(DLINP, LOGL_ERROR, + "unable to open socket: %s (line=%p, fd=%d)\n", sock_path, + line, config->fd.fd); + return ret; + } + LOGP(DLINP, LOGL_DEBUG, + "successfully opend (new) socket: %s (line=%p, fd=%d, ret=%d)\n", + sock_path, line, config->fd.fd, ret); + config->fd.fd = ret; + + /* Register socket in select loop */ + if (osmo_fd_register(&config->fd) < 0) { + LOGP(DLINP, LOGL_ERROR, + "error registering new socket (line=%p, fd=%d)\n", + line, config->fd.fd); + close(config->fd.fd); + return -EIO; + } + + /* Set line parameter */ + for (i = 0; i < ARRAY_SIZE(line->ts); i++) { + struct e1inp_ts *e1i_ts = &line->ts[i]; + if (!e1i_ts->lapd) { + e1i_ts->lapd = lapd_instance_alloc(1, + unixsocket_write_msg_lapd_cb, &config->fd, + e1inp_dlsap_up, e1i_ts, &lapd_profile_abis); + } + } + + /* Ensure ericsson-superchannel is turned of when + * a new connection is made */ + e1inp_ericsson_set_altc(line, 0); + + return ret; +} + +struct e1inp_driver unixsocket_driver = { + .name = "unixsocket", + .want_write = ts_want_write, + .line_update = unixsocket_line_update, + .default_delay = 0, +}; + +void e1inp_unixsocket_init(void) +{ + tall_unixsocket_ctx = talloc_named_const(libosmo_abis_ctx, 1, "unixsocket"); + e1inp_driver_register(&unixsocket_driver); +} + +void e1inp_ericsson_set_altc(struct e1inp_line *unixline, int superchannel) +{ + struct unixsocket_line *config; + struct msgb *msg; + + if (!unixline) + return; + + if (unixline->driver != &unixsocket_driver) { + LOGP(DLMI, LOGL_NOTICE, "altc is only supported by unixsocket\n"); + return; + } + + config = unixline->driver_data; + if (!config) { + LOGP(DLMI, LOGL_NOTICE, "e1inp driver not yet initialized.\n"); + return; + } + + + msg = msgb_alloc_headroom(200, 100, "ALTC"); + + /* version header */ + msgb_put_u8(msg, UNIXSOCKET_PROTO_VERSION); + /* data|control */ + msgb_put_u8(msg, UNIXSOCKET_PROTO_CONTROL); + + /* magic */ + msgb_put_u32(msg, 0x23004200); + msgb_put_u8(msg, superchannel ? 1 : 0); + + unixsocket_write_msg(msg, &config->fd); +} +