commit 8bf16493f68fd53c6e14f1612caf7425c73b2f4d Author: Pablo Neira Ayuso Date: Mon Oct 3 22:09:45 2011 +0200 initial commit diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..7df4729 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,14 @@ +AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6 +ACLOCAL_AMFLAGS = -I m4 + +INCLUDES = $(all_includes) -I$(top_srcdir)/include +SUBDIRS = include src examples + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libosmonetif.pc + +BUILT_SOURCES = $(top_srcdir)/.version +$(top_srcdir)/.version: + echo $(VERSION) > $@-t && mv $@-t $@ +dist-hook: + echo $(VERSION) > $(distdir)/.tarball-version diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..5e6e497 --- /dev/null +++ b/configure.ac @@ -0,0 +1,53 @@ +AC_INIT([libosmo-netif], + m4_esyscmd([./git-version-gen .tarball-version]), + [openbsc-devel@lists.openbsc.org]) + +AM_INIT_AUTOMAKE([dist-bzip2]) + +dnl kernel style compile messages +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +dnl checks for programs +AC_PROG_MAKE_SET +AC_PROG_CC +AC_PROG_INSTALL +LT_INIT +AC_PROG_LIBTOOL + +AC_CONFIG_MACRO_DIR([m4]) + +dnl checks for header files +AC_HEADER_STDC +AC_CHECK_HEADERS(execinfo.h sys/select.h sys/socket.h syslog.h ctype.h) + +# The following test is taken from WebKit's webkit.m4 +saved_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS -fvisibility=hidden " +AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden]) +AC_COMPILE_IFELSE([AC_LANG_SOURCE([char foo;])], + [ AC_MSG_RESULT([yes]) + SYMBOL_VISIBILITY="-fvisibility=hidden"], + AC_MSG_RESULT([no])) +CFLAGS="$saved_CFLAGS" +AC_SUBST(SYMBOL_VISIBILITY) + +dnl Generate the output +AM_CONFIG_HEADER(config.h) + +PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.3.0) +PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.3.0) +PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.3.0) +dnl FIXME: We depend on libosmoabis by now until we can move LAPD code here +PKG_CHECK_MODULES(LIBOSMOABIS, libosmoabis >= 0.0.7) +PKG_CHECK_MODULES(ORTP, ortp >= 0.15.0) + +AC_CHECK_HEADERS(dahdi/user.h,,AC_MSG_WARN(DAHDI input driver will not be built)) + +AC_OUTPUT( + libosmonetif.pc + include/Makefile + include/osmocom/Makefile + include/osmocom/netif/Makefile + src/Makefile + examples/Makefile + Makefile) diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100644 index 0000000..981128b --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,16 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS=-Wall -g $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(COVERAGE_LDFLAGS) + +noinst_PROGRAMS = lapd-over-stream-client \ + lapd-over-stream-server + +lapd_over_stream_client_SOURCES = lapd-over-stream-client.c +lapd_over_stream_client_LDADD = $(top_builddir)/src/libosmonetif.la \ + $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOABIS_LIBS) + +lapd_over_stream_server_SOURCES = lapd-over-stream-server.c +lapd_over_stream_server_LDADD = $(top_builddir)/src/libosmonetif.la \ + $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOABIS_LIBS) diff --git a/examples/lapd-over-stream-client.c b/examples/lapd-over-stream-client.c new file mode 100644 index 0000000..1525b7c --- /dev/null +++ b/examples/lapd-over-stream-client.c @@ -0,0 +1,187 @@ +/* LAPD over stream (user-mode/client) example. */ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#define DLAPDTEST 0 + +struct log_info_cat lapd_test_cat[] = { + [DLAPDTEST] = { + .name = "DLAPDTEST", + .description = "LAPD-mode test", + .color = "\033[1;35m", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, +}; + +const struct log_info lapd_test_log_info = { + .filter_fn = NULL, + .cat = lapd_test_cat, + .num_cat = ARRAY_SIZE(lapd_test_cat), +}; + +static struct stream_client_conn *conn; +static struct lapd_instance *lapd; +static int sapi = 63, tei = 0; + +void sighandler(int foo) +{ + lapd_sap_stop(lapd, tei, sapi); + lapd_instance_free(lapd); + LOGP(DLINP, LOGL_NOTICE, "closing LAPD.\n"); + exit(EXIT_SUCCESS); +} + +static int connect_cb(struct stream_client_conn *conn) +{ + LOGP(DLINP, LOGL_NOTICE, "connected\n"); + if (lapd_sap_start(lapd, tei, sapi) < 0) { + LOGP(DLINP, LOGL_ERROR, "cannot start user-side LAPD\n"); + exit(EXIT_FAILURE); + } + return 0; +} + +static int read_cb(struct stream_client_conn *conn, struct msgb *msg) +{ + int error; + + LOGP(DLINP, LOGL_NOTICE, "received message from stream\n"); + + if (lapd_receive(lapd, msg, &error) < 0) { + LOGP(DLINP, LOGL_ERROR, "lapd_receive returned error!\n"); + return -1; + } + return 0; +} + +static void *tall_test; + +void lapd_tx_cb(struct msgb *msg, void *cbdata) +{ + LOGP(DLINP, LOGL_NOTICE, "sending message over stream\n"); + stream_client_conn_send(conn, msg); +} + +void lapd_rx_cb(struct osmo_dlsap_prim *dp, uint8_t tei, uint8_t sapi, + void *rx_cbdata) +{ + struct msgb *msg = dp->oph.msg; + + switch (dp->oph.primitive) { + case PRIM_DL_EST: + DEBUGP(DLAPDTEST, "DL_EST: sapi(%d) tei(%d)\n", sapi, tei); + break; + case PRIM_DL_REL: + DEBUGP(DLAPDTEST, "DL_REL: sapi(%d) tei(%d)\n", sapi, tei); + break; + case PRIM_DL_DATA: + case PRIM_DL_UNIT_DATA: + if (dp->oph.operation == PRIM_OP_INDICATION) { + msg->l2h = msg->l3h; + DEBUGP(DLAPDTEST, "RX: %s sapi=%d tei=%d\n", + osmo_hexdump(msgb_l2(msg), msgb_l2len(msg)), + sapi, tei); + return; + } + break; + case PRIM_MDL_ERROR: + DEBUGP(DLMI, "MDL_EERROR: cause(%d)\n", dp->u.error_ind.cause); + break; + default: + printf("ERROR: unknown prim\n"); + break; + } +} + +static int kbd_cb(struct osmo_fd *fd, unsigned int what) +{ + char buf[1024]; + struct msgb *msg; + uint8_t *ptr; + int ret; + + ret = read(STDIN_FILENO, buf, sizeof(buf)); + + LOGP(DLAPDTEST, LOGL_NOTICE, "read %d byte from keyboard\n", ret); + + msg = msgb_alloc(1024, "LAPD/test"); + if (msg == NULL) { + LOGP(DLINP, LOGL_ERROR, "lapd: cannot allocate message\n"); + return 0; + } + ptr = msgb_put(msg, strlen(buf)); + memcpy(ptr, buf, ret); + + lapd_transmit(lapd, tei, sapi, msg); + + LOGP(DLAPDTEST, LOGL_NOTICE, "message of %d bytes sent\n", msg->len); + + return 0; +} + +int main(void) +{ + struct osmo_fd *kbd_ofd; + + tall_test = talloc_named_const(NULL, 1, "lapd_test"); + + osmo_init_logging(&lapd_test_log_info); + log_set_log_level(osmo_stderr_target, 1); + /* + * initialize LAPD stuff. + */ + + lapd = lapd_instance_alloc(0, lapd_tx_cb, NULL, lapd_rx_cb, NULL, + &lapd_profile_sat); + if (lapd == NULL) { + LOGP(DLINP, LOGL_ERROR, "cannot allocate instance\n"); + exit(EXIT_FAILURE); + } + + /* + * initialize stream client. + */ + + conn = stream_client_conn_create(tall_test); + if (conn == NULL) { + fprintf(stderr, "cannot create client\n"); + exit(EXIT_FAILURE); + } + stream_client_conn_set_addr(conn, "127.0.0.1"); + stream_client_conn_set_port(conn, 10000); + stream_client_conn_set_connect_cb(conn, connect_cb); + stream_client_conn_set_read_cb(conn, read_cb); + + if (stream_client_conn_open(conn) < 0) { + fprintf(stderr, "cannot open client\n"); + exit(EXIT_FAILURE); + } + + kbd_ofd = talloc_zero(tall_test, struct osmo_fd); + if (!kbd_ofd) { + LOGP(DLAPDTEST, LOGL_ERROR, "OOM\n"); + exit(EXIT_FAILURE); + } + kbd_ofd->fd = STDIN_FILENO; + kbd_ofd->when = BSC_FD_READ; + kbd_ofd->data = conn; + kbd_ofd->cb = kbd_cb; + osmo_fd_register(kbd_ofd); + + LOGP(DLINP, LOGL_NOTICE, "Entering main loop\n"); + + while(1) { + osmo_select_main(0); + } +} diff --git a/examples/lapd-over-stream-server.c b/examples/lapd-over-stream-server.c new file mode 100644 index 0000000..8d06f62 --- /dev/null +++ b/examples/lapd-over-stream-server.c @@ -0,0 +1,197 @@ +/* LAPD over stream (network-mode/server) example. */ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +static void *tall_test; + +#define DLAPDTEST 0 + +struct log_info_cat lapd_test_cat[] = { + [DLAPDTEST] = { + .name = "DLAPDTEST", + .description = "LAPD-mode test", + .color = "\033[1;35m", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, +}; + +const struct log_info lapd_test_log_info = { + .filter_fn = NULL, + .cat = lapd_test_cat, + .num_cat = ARRAY_SIZE(lapd_test_cat), +}; + +static struct stream_server_link *server; +static struct lapd_instance *lapd; +static int sapi = 63, tei = 0; + +void sighandler(int foo) +{ + lapd_instance_free(lapd); + LOGP(DLINP, LOGL_NOTICE, "closing LAPD.\n"); + exit(EXIT_SUCCESS); +} + +int read_cb(struct stream_server_conn *conn, struct msgb *msg) +{ + int error; + + LOGP(DLINP, LOGL_NOTICE, "received message from stream\n"); + + if (lapd_receive(lapd, msg, &error) < 0) { + LOGP(DLINP, LOGL_ERROR, "lapd_receive returned error!\n"); + return -1; + } + return 0; +} + +void lapd_tx_cb(struct msgb *msg, void *cbdata) +{ + struct stream_server_conn *conn = cbdata; + + LOGP(DLINP, LOGL_NOTICE, "sending message over stream\n"); + stream_server_conn_send(conn, msg); +} + +void lapd_rx_cb(struct osmo_dlsap_prim *dp, uint8_t tei, uint8_t sapi, + void *rx_cbdata) +{ + struct msgb *msg = dp->oph.msg; + + switch (dp->oph.primitive) { + case PRIM_DL_EST: + DEBUGP(DLAPDTEST, "DL_EST: sapi(%d) tei(%d)\n", sapi, tei); + break; + case PRIM_DL_REL: + DEBUGP(DLAPDTEST, "DL_REL: sapi(%d) tei(%d)\n", sapi, tei); + break; + case PRIM_DL_DATA: + case PRIM_DL_UNIT_DATA: + if (dp->oph.operation == PRIM_OP_INDICATION) { + msg->l2h = msg->l3h; + DEBUGP(DLAPDTEST, "RX: %s sapi=%d tei=%d\n", + osmo_hexdump(msgb_l2(msg), msgb_l2len(msg)), + sapi, tei); + return; + } + break; + case PRIM_MDL_ERROR: + DEBUGP(DLMI, "MDL_EERROR: cause(%d)\n", dp->u.error_ind.cause); + break; + default: + printf("ERROR: unknown prim\n"); + break; + } +} + +static int accept_cb(struct stream_server_link *server, int fd) +{ + struct stream_server_conn *conn; + int teip; + + conn = stream_server_conn_create(tall_test, server, fd, read_cb, + NULL, NULL); + if (conn == NULL) { + LOGP(DLINP, LOGL_ERROR, "error in lapd_receive\n"); + return -1; + } + + /* + * initialize LAPD stuff. + */ + + lapd = lapd_instance_alloc(1, lapd_tx_cb, conn, lapd_rx_cb, conn, + &lapd_profile_sat); + if (lapd == NULL) { + LOGP(DLINP, LOGL_ERROR, "cannot allocate instance\n"); + exit(EXIT_FAILURE); + } + + teip = lapd_tei_alloc(lapd, tei); + if (teip == 0) { + LOGP(DLINP, LOGL_ERROR, "cannot assign TEI\n"); + exit(EXIT_FAILURE); + } + return 0; +} + +static int kbd_cb(struct osmo_fd *fd, unsigned int what) +{ + char buf[1024]; + struct msgb *msg; + uint8_t *ptr; + int ret; + + ret = read(STDIN_FILENO, buf, sizeof(buf)); + + LOGP(DLAPDTEST, LOGL_NOTICE, "read %d byte from keyboard\n", ret); + + msg = msgb_alloc_headroom(1024, 128, "lapd_test"); + if (msg == NULL) { + LOGP(DLINP, LOGL_ERROR, "lapd: cannot allocate message\n"); + return 0; + } + ptr = msgb_put(msg, strlen(buf)); + memcpy(ptr, buf, strlen(buf)); + lapd_transmit(lapd, tei, sapi, msg); + + LOGP(DLAPDTEST, LOGL_NOTICE, "message of %d bytes sent\n", msg->len); + + return 0; +} + +int main(void) +{ + struct osmo_fd *kbd_ofd; + + tall_test = talloc_named_const(NULL, 1, "lapd_test"); + + osmo_init_logging(&lapd_test_log_info); + log_set_log_level(osmo_stderr_target, 1); + + /* + * initialize stream server. + */ + + server = stream_server_link_create(tall_test); + if (server == NULL) { + fprintf(stderr, "cannot create client\n"); + exit(EXIT_FAILURE); + } + stream_server_link_set_addr(server, "127.0.0.1"); + stream_server_link_set_port(server, 10000); + stream_server_link_set_accept_cb(server, accept_cb); + + if (stream_server_link_open(server) < 0) { + fprintf(stderr, "cannot open client\n"); + exit(EXIT_FAILURE); + } + + kbd_ofd = talloc_zero(tall_test, struct osmo_fd); + if (!kbd_ofd) { + LOGP(DLAPDTEST, LOGL_ERROR, "OOM\n"); + exit(EXIT_FAILURE); + } + kbd_ofd->fd = STDIN_FILENO; + kbd_ofd->when = BSC_FD_READ; + kbd_ofd->data = server; + kbd_ofd->cb = kbd_cb; + osmo_fd_register(kbd_ofd); + + LOGP(DLINP, LOGL_NOTICE, "Entering main loop\n"); + + while(1) { + osmo_select_main(0); + } +} diff --git a/git-version-gen b/git-version-gen new file mode 100755 index 0000000..42cf3d2 --- /dev/null +++ b/git-version-gen @@ -0,0 +1,151 @@ +#!/bin/sh +# Print a version string. +scriptversion=2010-01-28.01 + +# Copyright (C) 2007-2010 Free Software Foundation, Inc. +# +# 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 3 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, see . + +# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/. +# It may be run two ways: +# - from a git repository in which the "git describe" command below +# produces useful output (thus requiring at least one signed tag) +# - from a non-git-repo directory containing a .tarball-version file, which +# presumes this script is invoked like "./git-version-gen .tarball-version". + +# In order to use intra-version strings in your project, you will need two +# separate generated version string files: +# +# .tarball-version - present only in a distribution tarball, and not in +# a checked-out repository. Created with contents that were learned at +# the last time autoconf was run, and used by git-version-gen. Must not +# be present in either $(srcdir) or $(builddir) for git-version-gen to +# give accurate answers during normal development with a checked out tree, +# but must be present in a tarball when there is no version control system. +# Therefore, it cannot be used in any dependencies. GNUmakefile has +# hooks to force a reconfigure at distribution time to get the value +# correct, without penalizing normal development with extra reconfigures. +# +# .version - present in a checked-out repository and in a distribution +# tarball. Usable in dependencies, particularly for files that don't +# want to depend on config.h but do want to track version changes. +# Delete this file prior to any autoconf run where you want to rebuild +# files to pick up a version string change; and leave it stale to +# minimize rebuild time after unrelated changes to configure sources. +# +# It is probably wise to add these two files to .gitignore, so that you +# don't accidentally commit either generated file. +# +# Use the following line in your configure.ac, so that $(VERSION) will +# automatically be up-to-date each time configure is run (and note that +# since configure.ac no longer includes a version string, Makefile rules +# should not depend on configure.ac for version updates). +# +# AC_INIT([GNU project], +# m4_esyscmd([build-aux/git-version-gen .tarball-version]), +# [bug-project@example]) +# +# Then use the following lines in your Makefile.am, so that .version +# will be present for dependencies, and so that .tarball-version will +# exist in distribution tarballs. +# +# BUILT_SOURCES = $(top_srcdir)/.version +# $(top_srcdir)/.version: +# echo $(VERSION) > $@-t && mv $@-t $@ +# dist-hook: +# echo $(VERSION) > $(distdir)/.tarball-version + +case $# in + 1) ;; + *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;; +esac + +tarball_version_file=$1 +nl=' +' + +# First see if there is a tarball-only version file. +# then try "git describe", then default. +if test -f $tarball_version_file +then + v=`cat $tarball_version_file` || exit 1 + case $v in + *$nl*) v= ;; # reject multi-line output + [0-9]*) ;; + *) v= ;; + esac + test -z "$v" \ + && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2 +fi + +if test -n "$v" +then + : # use $v +elif + v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \ + || git describe --abbrev=4 HEAD 2>/dev/null` \ + && case $v in + [0-9]*) ;; + v[0-9]*) ;; + *) (exit 1) ;; + esac +then + # Is this a new git that lists number of commits since the last + # tag or the previous older version that did not? + # Newer: v6.10-77-g0f8faeb + # Older: v6.10-g0f8faeb + case $v in + *-*-*) : git describe is okay three part flavor ;; + *-*) + : git describe is older two part flavor + # Recreate the number of commits and rewrite such that the + # result is the same as if we were using the newer version + # of git describe. + vtag=`echo "$v" | sed 's/-.*//'` + numcommits=`git rev-list "$vtag"..HEAD | wc -l` + v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`; + ;; + esac + + # Change the first '-' to a '.', so version-comparing tools work properly. + # Remove the "g" in git describe's output string, to save a byte. + v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`; +else + v=UNKNOWN +fi + +v=`echo "$v" |sed 's/^v//'` + +# Don't declare a version "dirty" merely because a time stamp has changed. +git status > /dev/null 2>&1 + +dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty= +case "$dirty" in + '') ;; + *) # Append the suffix only if there isn't one already. + case $v in + *-dirty) ;; + *) v="$v-dirty" ;; + esac ;; +esac + +# Omit the trailing newline, so that m4_esyscmd can use the result directly. +echo "$v" | tr -d '\012' + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-end: "$" +# End: diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 0000000..b255997 --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=osmocom diff --git a/include/osmocom/Makefile.am b/include/osmocom/Makefile.am new file mode 100644 index 0000000..df0aef9 --- /dev/null +++ b/include/osmocom/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=netif diff --git a/include/osmocom/netif/Makefile.am b/include/osmocom/netif/Makefile.am new file mode 100644 index 0000000..8b68362 --- /dev/null +++ b/include/osmocom/netif/Makefile.am @@ -0,0 +1,3 @@ +osmonetif_HEADERS = stream.h + +osmonetifdir = $(includedir)/osmocom/netif diff --git a/include/osmocom/netif/stream.h b/include/osmocom/netif/stream.h new file mode 100644 index 0000000..4f6407a --- /dev/null +++ b/include/osmocom/netif/stream.h @@ -0,0 +1,44 @@ +#ifndef _OSMO_STREAM_H_ +#define _OSMO_STREAM_H_ + +#include +#include +#include +#include + +struct stream_server_link; + +struct stream_server_link *stream_server_link_create(void *ctx); +void stream_server_link_destroy(struct stream_server_link *link); + +void stream_server_link_set_addr(struct stream_server_link *link, const char *addr); +void stream_server_link_set_port(struct stream_server_link *link, uint16_t port); +void stream_server_link_set_accept_cb(struct stream_server_link *link, int (*accept_cb)(struct stream_server_link *link, int fd)); + +int stream_server_link_open(struct stream_server_link *link); +void stream_server_link_close(struct stream_server_link *link); + +struct stream_server_conn; + +struct stream_server_conn *stream_server_conn_create(void *ctx, struct stream_server_link *link, int fd, int (*cb)(struct stream_server_conn *conn, struct msgb *msg), int (*closed_cb)(struct stream_server_conn *conn), void *data); +void stream_server_conn_destroy(struct stream_server_conn *conn); + +void stream_server_conn_send(struct stream_server_conn *conn, struct msgb *msg); + +struct stream_client_conn; + +void stream_client_conn_set_addr(struct stream_client_conn *link, const char *addr); +void stream_client_conn_set_port(struct stream_client_conn *link, uint16_t port); +void stream_client_conn_set_data(struct stream_client_conn *link, void *data); +void stream_client_conn_set_connect_cb(struct stream_client_conn *link, int (*connect_cb)(struct stream_client_conn *link)); +void stream_client_conn_set_read_cb(struct stream_client_conn *link, int (*read_cb)(struct stream_client_conn *link, struct msgb *msgb)); + +struct stream_client_conn *stream_client_conn_create(void *ctx); +void stream_client_conn_destroy(struct stream_client_conn *link); + +int stream_client_conn_open(struct stream_client_conn *link); +void stream_client_conn_close(struct stream_client_conn *link); + +void stream_client_conn_send(struct stream_client_conn *link, struct msgb *msg); + +#endif diff --git a/libosmonetif.pc.in b/libosmonetif.pc.in new file mode 100644 index 0000000..21ad05f --- /dev/null +++ b/libosmonetif.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Osmocom network interface library +Description: C Utility Library +Version: @VERSION@ +Libs: -L${libdir} -losmoabis +Cflags: -I${includedir}/ diff --git a/m4/.gitignore b/m4/.gitignore new file mode 100644 index 0000000..64d9bbc --- /dev/null +++ b/m4/.gitignore @@ -0,0 +1,2 @@ +/libtool.m4 +/lt*.m4 diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..30e1e72 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,11 @@ +# 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 + +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS= -fPIC -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS) + +lib_LTLIBRARIES = libosmonetif.la + +libosmonetif_la_SOURCES = stream.c diff --git a/src/stream.c b/src/stream.c new file mode 100644 index 0000000..2603ecb --- /dev/null +++ b/src/stream.c @@ -0,0 +1,484 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +/* + * Client side. + */ + +enum stream_client_conn_state { + STREAM_CLIENT_LINK_STATE_NONE = 0, + STREAM_CLIENT_LINK_STATE_CONNECTING = 1, + STREAM_CLIENT_LINK_STATE_CONNECTED = 2, + STREAM_CLIENT_LINK_STATE_MAX +}; + +struct stream_client_conn { + struct osmo_fd ofd; + struct llist_head tx_queue; + struct osmo_timer_list timer; + enum stream_client_conn_state state; + const char *addr; + uint16_t port; + int (*connect_cb)(struct stream_client_conn *link); + int (*read_cb)(struct stream_client_conn *link, struct msgb *msg); + int (*write_cb)(struct stream_client_conn *link); + void *data; +}; + +static int stream_msg_recv(int fd, struct msgb *msg) +{ + int ret; + + ret = recv(fd, msg->data, msg->data_len, 0); + if (ret <= 0) + return ret; + + msgb_put(msg, ret); + return ret; +} + +void stream_client_conn_close(struct stream_client_conn *link); + +static void stream_client_retry(struct stream_client_conn *link) +{ + LOGP(DLINP, LOGL_DEBUG, "connection closed\n"); + stream_client_conn_close(link); + LOGP(DLINP, LOGL_DEBUG, "retrying in 5 seconds...\n"); + osmo_timer_schedule(&link->timer, 5, 0); + link->state = STREAM_CLIENT_LINK_STATE_CONNECTING; +} + +void stream_client_conn_close(struct stream_client_conn *link) +{ + osmo_fd_unregister(&link->ofd); + close(link->ofd.fd); +} + +static void stream_client_read(struct stream_client_conn *link) +{ + struct msgb *msg; + int ret; + + LOGP(DLINP, LOGL_DEBUG, "message received\n"); + + msg = msgb_alloc(1200, "LAPD/client"); + if (!msg) { + LOGP(DLINP, LOGL_ERROR, "cannot allocate room for message\n"); + return; + } + ret = stream_msg_recv(link->ofd.fd, msg); + if (ret < 0) { + if (errno == EPIPE || errno == ECONNRESET) { + LOGP(DLINP, LOGL_ERROR, "lost connection with server\n"); + } + stream_client_retry(link); + return; + } else if (ret == 0) { + LOGP(DLINP, LOGL_ERROR, "connection closed with server\n"); + stream_client_retry(link); + return; + } + msgb_put(msg, ret); + if (link->read_cb) + link->read_cb(link, msg); +} + +static int stream_client_write(struct stream_client_conn *link) +{ + struct msgb *msg; + struct llist_head *lh; + int ret; + + LOGP(DLINP, LOGL_DEBUG, "sending data\n"); + + if (llist_empty(&link->tx_queue)) { + link->ofd.when &= ~BSC_FD_WRITE; + return 0; + } + lh = link->tx_queue.next; + llist_del(lh); + msg = llist_entry(lh, struct msgb, list); + + if (link->state == STREAM_CLIENT_LINK_STATE_CONNECTING) { + LOGP(DLINP, LOGL_ERROR, "not connected, dropping data!\n"); + return 0; + } + + ret = send(link->ofd.fd, msg->data, msg->len, 0); + if (ret < 0) { + if (errno == EPIPE || errno == ENOTCONN) { + stream_client_retry(link); + } + LOGP(DLINP, LOGL_ERROR, "error to send\n"); + } + msgb_free(msg); + return 0; +} + +static int stream_client_fd_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct stream_client_conn *link = ofd->data; + int error, ret; + socklen_t len = sizeof(error); + + switch(link->state) { + case STREAM_CLIENT_LINK_STATE_CONNECTING: + ret = getsockopt(ofd->fd, SOL_SOCKET, SO_ERROR, &error, &len); + if (ret >= 0 && error > 0) { + stream_client_retry(link); + return 0; + } + ofd->when &= ~BSC_FD_WRITE; + LOGP(DLINP, LOGL_DEBUG, "connection done.\n"); + link->state = STREAM_CLIENT_LINK_STATE_CONNECTED; + if (link->connect_cb) + link->connect_cb(link); + break; + case STREAM_CLIENT_LINK_STATE_CONNECTED: + if (what & BSC_FD_READ) { + LOGP(DLINP, LOGL_DEBUG, "connected read\n"); + stream_client_read(link); + } + if (what & BSC_FD_WRITE) { + LOGP(DLINP, LOGL_DEBUG, "connected write\n"); + stream_client_write(link); + } + break; + default: + break; + } + return 0; +} + +static void link_timer_cb(void *data); + +struct stream_client_conn *stream_client_conn_create(void *ctx) +{ + struct stream_client_conn *link; + + link = talloc_zero(ctx, struct stream_client_conn); + if (!link) + return NULL; + + link->ofd.when |= BSC_FD_READ | BSC_FD_WRITE; + link->ofd.priv_nr = 0; /* XXX */ + link->ofd.cb = stream_client_fd_cb; + link->ofd.data = link; + link->state = STREAM_CLIENT_LINK_STATE_CONNECTING; + link->timer.cb = link_timer_cb; + link->timer.data = link; + INIT_LLIST_HEAD(&link->tx_queue); + + return link; +} + +void +stream_client_conn_set_addr(struct stream_client_conn *link, const char *addr) +{ + link->addr = talloc_strdup(link, addr); +} + +void +stream_client_conn_set_port(struct stream_client_conn *link, uint16_t port) +{ + link->port = port; +} + +void +stream_client_conn_set_data(struct stream_client_conn *link, void *data) +{ + link->data = data; +} + +void +stream_client_conn_set_connect_cb(struct stream_client_conn *link, + int (*connect_cb)(struct stream_client_conn *link)) +{ + link->connect_cb = connect_cb; +} + +void +stream_client_conn_set_read_cb(struct stream_client_conn *link, + int (*read_cb)(struct stream_client_conn *link, struct msgb *msgb)) +{ + link->read_cb = read_cb; +} + +void stream_client_conn_destroy(struct stream_client_conn *link) +{ + talloc_free(link); +} + +int stream_client_conn_open(struct stream_client_conn *link) +{ + int ret; + + ret = osmo_sock_init(AF_INET, SOCK_STREAM, IPPROTO_TCP, + link->addr, link->port, + OSMO_SOCK_F_CONNECT|OSMO_SOCK_F_NONBLOCK); + if (ret < 0) { + if (errno != EINPROGRESS) + return ret; + } + link->ofd.fd = ret; + if (osmo_fd_register(&link->ofd) < 0) { + close(ret); + return -EIO; + } + return 0; +} + +static void link_timer_cb(void *data) +{ + struct stream_client_conn *link = data; + + LOGP(DLINP, LOGL_DEBUG, "reconnecting.\n"); + + switch(link->state) { + case STREAM_CLIENT_LINK_STATE_CONNECTING: + stream_client_conn_open(link); + break; + default: + break; + } +} + +void stream_client_conn_send(struct stream_client_conn *link, struct msgb *msg) +{ + msgb_enqueue(&link->tx_queue, msg); + link->ofd.when |= BSC_FD_WRITE; +} + +/* + * Server side. + */ + +struct stream_server_link { + struct osmo_fd ofd; + const char *addr; + uint16_t port; + int (*accept_cb)(struct stream_server_link *link, int fd); + void *data; +}; + +static int stream_server_fd_cb(struct osmo_fd *ofd, unsigned int what) +{ + int ret; + struct sockaddr_in sa; + socklen_t sa_len = sizeof(sa); + struct stream_server_link *link = ofd->data; + + ret = accept(ofd->fd, (struct sockaddr *)&sa, &sa_len); + if (ret < 0) { + LOGP(DLINP, LOGL_ERROR, "failed to accept from origin " + "peer, reason=`%s'\n", strerror(errno)); + return ret; + } + LOGP(DLINP, LOGL_DEBUG, "accept()ed new link from %s to port %u\n", + inet_ntoa(sa.sin_addr), link->port); + + if (link->accept_cb) + link->accept_cb(link, ret); + + return 0; +} + +struct stream_server_link *stream_server_link_create(void *ctx) +{ + struct stream_server_link *link; + + link = talloc_zero(ctx, struct stream_server_link); + if (!link) + return NULL; + + link->ofd.when |= BSC_FD_READ | BSC_FD_WRITE; + link->ofd.cb = stream_server_fd_cb; + link->ofd.data = link; + + return link; +} + +void stream_server_link_set_addr(struct stream_server_link *link, const char *addr) +{ + link->addr = talloc_strdup(link, addr); +} + +void stream_server_link_set_port(struct stream_server_link *link, uint16_t port) +{ + link->port = port; +} + +void stream_server_link_set_accept_cb(struct stream_server_link *link, + int (*accept_cb)(struct stream_server_link *link, int fd)) + +{ + link->accept_cb = accept_cb; +} + +void stream_server_link_destroy(struct stream_server_link *link) +{ + talloc_free(link); +} + +int stream_server_link_open(struct stream_server_link *link) +{ + int ret; + + ret = osmo_sock_init(AF_INET, SOCK_STREAM, IPPROTO_TCP, + link->addr, link->port, OSMO_SOCK_F_BIND); + if (ret < 0) + return ret; + + link->ofd.fd = ret; + if (osmo_fd_register(&link->ofd) < 0) { + close(ret); + return -EIO; + } + return 0; +} + +void stream_server_link_close(struct stream_server_link *link) +{ + osmo_fd_unregister(&link->ofd); + close(link->ofd.fd); +} + +struct stream_server_conn { + struct stream_server_link *server; + struct osmo_fd ofd; + struct llist_head tx_queue; + int (*closed_cb)(struct stream_server_conn *peer); + int (*cb)(struct stream_server_conn *peer, struct msgb *msg); + void *data; +}; + +static void stream_server_conn_read(struct stream_server_conn *conn) +{ + struct msgb *msg; + int ret; + + LOGP(DLINP, LOGL_DEBUG, "message received\n"); + + msg = msgb_alloc(1200, "LAPD/client"); + if (!msg) { + LOGP(DLINP, LOGL_ERROR, "cannot allocate room for message\n"); + return; + } + ret = stream_msg_recv(conn->ofd.fd, msg); + if (ret < 0) { + if (errno == EPIPE || errno == ECONNRESET) { + LOGP(DLINP, LOGL_ERROR, "lost connection with server\n"); + } + stream_server_conn_destroy(conn); + return; + } else if (ret == 0) { + LOGP(DLINP, LOGL_ERROR, "connection closed with server\n"); + stream_server_conn_destroy(conn); + return; + } + msgb_put(msg, ret); + LOGP(DLINP, LOGL_NOTICE, "received %d bytes from client\n", ret); + if (conn->cb) + conn->cb(conn, msg); + + return; +} + +static void stream_server_conn_write(struct stream_server_conn *conn) +{ + struct msgb *msg; + struct llist_head *lh; + int ret; + + LOGP(DLINP, LOGL_DEBUG, "sending data\n"); + + if (llist_empty(&conn->tx_queue)) { + conn->ofd.when &= ~BSC_FD_WRITE; + return; + } + lh = conn->tx_queue.next; + llist_del(lh); + msg = llist_entry(lh, struct msgb, list); + + ret = send(conn->ofd.fd, msg->data, msg->len, 0); + if (ret < 0) { + LOGP(DLINP, LOGL_ERROR, "error to send\n"); + } + msgb_free(msg); +} + +static int stream_server_conn_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct stream_server_conn *conn = ofd->data; + + LOGP(DLINP, LOGL_DEBUG, "connected read/write\n"); + if (what & BSC_FD_READ) + stream_server_conn_read(conn); + if (what & BSC_FD_WRITE) + stream_server_conn_write(conn); + + return 0; +} + +struct stream_server_conn * +stream_server_conn_create(void *ctx, struct stream_server_link *link, int fd, + int (*cb)(struct stream_server_conn *conn, struct msgb *msg), + int (*closed_cb)(struct stream_server_conn *conn), void *data) +{ + struct stream_server_conn *conn; + + conn = talloc_zero(ctx, struct stream_server_conn); + if (conn == NULL) { + LOGP(DLINP, LOGL_ERROR, "cannot allocate new peer in server, " + "reason=`%s'\n", strerror(errno)); + return NULL; + } + conn->server = link; + conn->ofd.fd = fd; + conn->ofd.data = conn; + conn->ofd.cb = stream_server_conn_cb; + conn->ofd.when = BSC_FD_READ; + conn->cb = cb; + conn->closed_cb = closed_cb; + conn->data = data; + INIT_LLIST_HEAD(&conn->tx_queue); + + if (osmo_fd_register(&conn->ofd) < 0) { + LOGP(DLINP, LOGL_ERROR, "could not register FD\n"); + talloc_free(conn); + return NULL; + } + return conn; +} + +void stream_server_conn_destroy(struct stream_server_conn *conn) +{ + close(conn->ofd.fd); + osmo_fd_unregister(&conn->ofd); + if (conn->closed_cb) + conn->closed_cb(conn); + talloc_free(conn); +} + +void stream_server_conn_send(struct stream_server_conn *conn, struct msgb *msg) +{ + msgb_enqueue(&conn->tx_queue, msg); + conn->ofd.when |= BSC_FD_WRITE; +}