gsmtap: rework GSMTAP API to be more future-proof

* use write_queue where applicable
* provide functions that work on raw FD and those with osmo_fd
* add support for multiple gsmtap instances (no global variables)
This commit is contained in:
Harald Welte 2011-05-21 18:54:32 +02:00
parent 8256076722
commit 33cb71ac91
6 changed files with 305 additions and 98 deletions

View File

@ -1,5 +1,5 @@
osmocore_HEADERS = signal.h linuxlist.h timer.h select.h msgb.h bits.h \
bitvec.h statistics.h utils.h \
bitvec.h statistics.h utils.h socket.h \
gsmtap.h write_queue.h \
logging.h rate_ctr.h gsmtap_util.h \
plugin.h crc16.h panic.h process.h msgfile.h \

View File

@ -2,24 +2,52 @@
#define _GSMTAP_UTIL_H
#include <stdint.h>
#include <osmocom/core/write_queue.h>
#include <osmocom/core/select.h>
/* convert RSL channel number to GSMTAP channel type */
uint8_t chantype_rsl2gsmtap(uint8_t rsl_chantype, uint8_t rsl_link_id);
/* receive a message from L1/L2 and put it in GSMTAP */
/* generate msgb from data + metadata */
struct msgb *gsmtap_makemsg(uint16_t arfcn, uint8_t ts, uint8_t chan_type,
uint8_t ss, uint32_t fn, int8_t signal_dbm,
uint8_t snr, const uint8_t *data, unsigned int len);
/* receive a message from L1/L2 and put it in GSMTAP */
int gsmtap_sendmsg(uint16_t arfcn, uint8_t ts, uint8_t chan_type, uint8_t ss,
uint32_t fn, int8_t signal_dbm, uint8_t snr,
const uint8_t *data, unsigned int len);
/* one gsmtap instance */
struct gsmtap_inst {
int ofd_wq_mode;
struct osmo_wqueue wq;
struct osmo_fd sink_ofd;
};
int gsmtap_init(uint32_t dst_ip);
static inline int gsmtap_inst_fd(struct gsmtap_inst *gti)
{
return gti->wq.bfd.fd;
}
/* Create a local 'gsmtap sink' avoiding the UDP packets being rejected
* with ICMP reject messages */
int gsmtap_sink_init(uint32_t bind_ip);
/* Open a GSMTAP source (sending) socket, conncet it to host/port and
* return resulting fd */
int gsmtap_source_init_fd(const char *host, uint16_t port);
/* Add a local sink to an existing GSMTAP source and return fd */
int gsmtap_source_add_sink_fd(int gsmtap_fd);
/* Open GSMTAP source (sending) socket, connect it to host/port,
* allocate 'struct gsmtap_inst' and optionally osmo_fd/osmo_wqueue
* registration */
struct gsmtap_inst *gsmtap_source_init(const char *host, uint16_t port,
int ofd_wq_mode);
/* Add a local sink to an existing GSMTAP source instance */
int gsmtap_source_add_sink(struct gsmtap_inst *gti);
/* Send a msgb through a GSMTAP source */
int gsmtap_sendmsg(struct gsmtap_inst *gti, struct msgb *msg);
/* generate a message and send it via GSMTAP */
int gsmtap_send(struct gsmtap_inst *gti, uint16_t arfcn, uint8_t ts,
uint8_t chan_type, uint8_t ss, uint32_t fn,
int8_t signal_dbm, uint8_t snr, const uint8_t *data,
unsigned int len);
#endif /* _GSMTAP_UTIL_H */

View File

@ -0,0 +1,16 @@
#ifndef _OSMOCORE_SOCKET_H
#define _OSMOCORE_SOCKET_H
#include <stdint.h>
#include <sys/socket.h>
int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,
const char *host, uint16_t port, int connect0_bind1);
int osmo_sock_init_sa(struct sockaddr *ss, uint16_t type,
uint8_t proto, int connect0_bind1);
/* determine if the given address is a local address */
int osmo_sockaddr_is_local(struct sockaddr *addr, socklen_t addrlen);
#endif /* _OSMOCORE_SOCKET_H */

View File

@ -2,7 +2,7 @@ SUBDIRS=. vty codec gsm
# 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=1:0:1
LIBVERSION=2:0:0
INCLUDES = $(all_includes) -I$(top_srcdir)/include
AM_CFLAGS = -fPIC -Wall
@ -11,7 +11,7 @@ lib_LTLIBRARIES = libosmocore.la
libosmocore_la_SOURCES = timer.c select.c signal.c msgb.c bits.c \
bitvec.c statistics.c \
write_queue.c utils.c \
write_queue.c utils.c socket.c \
logging.c logging_syslog.c rate_ctr.c \
gsmtap_util.c crc16.c panic.c backtrace.c \
process.c conv.c application.c

View File

@ -1,6 +1,6 @@
/* GSMTAP output for Osmocom Layer2 (will only work on the host PC) */
/* GSMTAP support code in libmsomcore */
/*
* (C) 2010 by Harald Welte <laforge@gnumonks.org>
* (C) 2010-2011 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
@ -22,18 +22,19 @@
#include "../config.h"
#ifdef HAVE_SYS_SELECT_H
#include <osmocom/core/gsmtap_util.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/gsmtap.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/gsm/rsl.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
@ -42,10 +43,6 @@
#include <string.h>
#include <errno.h>
static struct osmo_fd gsmtap_bfd = { .fd = -1 };
static struct osmo_fd gsmtap_sink_bfd = { .fd = -1 };
static LLIST_HEAD(gsmtap_txqueue);
uint8_t chantype_rsl2gsmtap(uint8_t rsl_chantype, uint8_t link_id)
{
uint8_t ret = GSMTAP_CHANNEL_UNKNOWN;
@ -114,44 +111,82 @@ struct msgb *gsmtap_makemsg(uint16_t arfcn, uint8_t ts, uint8_t chan_type,
return msg;
}
/* Open a GSMTAP source (sending) socket, conncet it to host/port and
* return resulting fd */
int gsmtap_source_init_fd(const char *host, uint16_t port)
{
if (port == 0)
port = GSMTAP_UDP_PORT;
if (host == NULL)
host = "localhost";
return osmo_sock_init(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, host, port, 0);
}
int gsmtap_source_add_sink_fd(int gsmtap_fd)
{
struct sockaddr_storage ss;
socklen_t ss_len = sizeof(ss);
int rc;
rc = getpeername(gsmtap_fd, (struct sockaddr *)&ss, &ss_len);
if (rc < 0)
return rc;
if (osmo_sockaddr_is_local((struct sockaddr *)&ss, ss_len) == 1) {
rc = osmo_sock_init_sa((struct sockaddr *)&ss, SOCK_DGRAM, IPPROTO_UDP, 1);
if (rc >= 0)
return rc;
}
return -ENODEV;
}
#ifdef HAVE_SYS_SELECT_H
int gsmtap_sendmsg(struct gsmtap_inst *gti, struct msgb *msg)
{
if (gti->ofd_wq_mode)
return osmo_wqueue_enqueue(&gti->wq, msg);
else {
/* try immediate send and return error if any */
int rc;
rc = write(gsmtap_inst_fd(gti), msg->data, msg->len);
if (rc <= 0) {
return rc;
} else if (rc >= msg->len) {
msgb_free(msg);
return 0;
} else {
/* short write */
return -EIO;
}
}
}
/* receive a message from L1/L2 and put it in GSMTAP */
int gsmtap_sendmsg(uint16_t arfcn, uint8_t ts, uint8_t chan_type, uint8_t ss,
uint32_t fn, int8_t signal_dbm, uint8_t snr,
const uint8_t *data, unsigned int len)
int gsmtap_send(struct gsmtap_inst *gti, uint16_t arfcn, uint8_t ts,
uint8_t chan_type, uint8_t ss, uint32_t fn,
int8_t signal_dbm, uint8_t snr, const uint8_t *data,
unsigned int len)
{
struct msgb *msg;
/* gsmtap was never initialized, so don't try to send anything */
if (gsmtap_bfd.fd == -1)
return 0;
msg = gsmtap_makemsg(arfcn, ts, chan_type, ss, fn, signal_dbm,
snr, data, len);
if (!msg)
return -ENOMEM;
msgb_enqueue(&gsmtap_txqueue, msg);
gsmtap_bfd.when |= BSC_FD_WRITE;
return 0;
return gsmtap_sendmsg(gti, msg);
}
/* Callback from select layer if we can write to the socket */
static int gsmtap_fd_cb(struct osmo_fd *fd, unsigned int flags)
static int gsmtap_wq_w_cb(struct osmo_fd *ofd, struct msgb *msg)
{
struct msgb *msg;
int rc;
if (!(flags & BSC_FD_WRITE))
return 0;
msg = msgb_dequeue(&gsmtap_txqueue);
if (!msg) {
/* no more messages in the queue, disable READ cb */
gsmtap_bfd.when = 0;
return 0;
}
rc = write(gsmtap_bfd.fd, msg->data, msg->len);
rc = write(ofd->fd, msg->data, msg->len);
if (rc < 0) {
perror("writing msgb to gsmtap fd");
msgb_free(msg);
@ -167,37 +202,6 @@ static int gsmtap_fd_cb(struct osmo_fd *fd, unsigned int flags)
return 0;
}
int gsmtap_init(uint32_t dst_ip)
{
int rc;
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(GSMTAP_UDP_PORT);
sin.sin_addr.s_addr = htonl(dst_ip);
/* create socket */
rc = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (rc < 0) {
perror("creating UDP socket");
return rc;
}
gsmtap_bfd.fd = rc;
rc = connect(rc, (struct sockaddr *)&sin, sizeof(sin));
if (rc < 0) {
perror("connecting UDP socket");
close(gsmtap_bfd.fd);
gsmtap_bfd.fd = -1;
return rc;
}
gsmtap_bfd.when = BSC_FD_WRITE;
gsmtap_bfd.cb = gsmtap_fd_cb;
gsmtap_bfd.data = NULL;
return osmo_fd_register(&gsmtap_bfd);
}
/* Callback from select layer if we can read from the sink socket */
static int gsmtap_sink_fd_cb(struct osmo_fd *fd, unsigned int flags)
{
@ -217,37 +221,53 @@ static int gsmtap_sink_fd_cb(struct osmo_fd *fd, unsigned int flags)
return 0;
}
/* Create a local 'gsmtap sink' avoiding the UDP packets being rejected
* with ICMP reject messages */
int gsmtap_sink_init(uint32_t bind_ip)
/* Add a local sink to an existing GSMTAP source instance */
int gsmtap_source_add_sink(struct gsmtap_inst *gti)
{
int rc;
struct sockaddr_in sin;
int fd;
sin.sin_family = AF_INET;
sin.sin_port = htons(GSMTAP_UDP_PORT);
sin.sin_addr.s_addr = htonl(bind_ip);
fd = gsmtap_source_add_sink_fd(gsmtap_inst_fd(gti));
if (fd < 0)
return fd;
rc = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (rc < 0) {
perror("creating UDP socket");
return rc;
}
gsmtap_sink_bfd.fd = rc;
rc = bind(rc, (struct sockaddr *)&sin, sizeof(sin));
if (rc < 0) {
perror("binding UDP socket");
close(gsmtap_sink_bfd.fd);
gsmtap_sink_bfd.fd = -1;
return rc;
if (gti->ofd_wq_mode) {
struct osmo_fd *sink_ofd;
sink_ofd = &gti->sink_ofd;
sink_ofd->fd = fd;
sink_ofd->when = BSC_FD_READ;
sink_ofd->cb = gsmtap_sink_fd_cb;
osmo_fd_register(sink_ofd);
}
gsmtap_sink_bfd.when = BSC_FD_READ;
gsmtap_sink_bfd.cb = gsmtap_sink_fd_cb;
gsmtap_sink_bfd.data = NULL;
return fd;
}
return osmo_fd_register(&gsmtap_sink_bfd);
/* like gsmtap_init2() but integrated with libosmocore select.c */
struct gsmtap_inst *gsmtap_source_init(const char *host, uint16_t port,
int ofd_wq_mode)
{
struct gsmtap_inst *gti;
int fd;
fd = gsmtap_source_init_fd(host, port);
if (fd < 0)
return NULL;
gti = talloc_zero(NULL, struct gsmtap_inst);
gti->ofd_wq_mode = ofd_wq_mode;
gti->wq.bfd.fd = fd;
gti->sink_ofd.fd = -1;
if (ofd_wq_mode) {
osmo_wqueue_init(&gti->wq, 64);
gti->wq.write_cb = &gsmtap_wq_w_cb;
osmo_fd_register(&gti->wq.bfd);
}
return gti;
}
#endif /* HAVE_SYS_SELECT_H */

143
src/socket.c Normal file
View File

@ -0,0 +1,143 @@
#include "../config.h"
#include <osmocom/core/logging.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <netdb.h>
#include <ifaddrs.h>
int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,
const char *host, uint16_t port, int connect0_bind1)
{
struct addrinfo hints, *result, *rp;
int sfd, rc;
char portbuf[16];
sprintf(portbuf, "%u", port);
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = family;
hints.ai_socktype = type;
hints.ai_flags = 0;
hints.ai_protocol = proto;
rc = getaddrinfo(host, portbuf, &hints, &result);
if (rc != 0) {
perror("getaddrinfo returned NULL");
return -EINVAL;
}
for (rp = result; rp != NULL; rp = rp->ai_next) {
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sfd == -1)
continue;
if (connect0_bind1 == 0) {
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
break;
} else {
if (bind(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
break;
}
close(sfd);
}
freeaddrinfo(result);
if (rp == NULL) {
perror("unable to connect/bind socket");
return -ENODEV;
}
return sfd;
}
int osmo_sock_init_sa(struct sockaddr *ss, uint16_t type,
uint8_t proto, int connect0_bind1)
{
char host[NI_MAXHOST];
uint16_t port;
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
int s, sa_len;
/* determine port and host from ss */
switch (ss->sa_family) {
case AF_INET:
sin = (struct sockaddr_in *) ss;
sa_len = sizeof(struct sockaddr_in);
port = ntohs(sin->sin_port);
break;
case AF_INET6:
sin6 = (struct sockaddr_in6 *) ss;
sa_len = sizeof(struct sockaddr_in6);
port = ntohs(sin6->sin6_port);
break;
default:
return -EINVAL;
}
fprintf(stderr, "==> PORT = %u\n", port);
s = getnameinfo(ss, sa_len, host, NI_MAXHOST,
NULL, 0, NI_NUMERICHOST);
if (s != 0) {
perror("getnameinfo failed");
return s;
}
return osmo_sock_init(ss->sa_family, type, proto, host,
port, connect0_bind1);
}
static int sockaddr_equal(const struct sockaddr *a,
const struct sockaddr *b, socklen_t len)
{
struct sockaddr_in *sin_a, *sin_b;
struct sockaddr_in6 *sin6_a, *sin6_b;
if (a->sa_family != b->sa_family)
return 0;
switch (a->sa_family) {
case AF_INET:
sin_a = (struct sockaddr_in *)a;
sin_b = (struct sockaddr_in *)b;
if (!memcmp(&sin_a->sin_addr, &sin_b->sin_addr,
sizeof(struct in_addr)))
return 1;
break;
case AF_INET6:
sin6_a = (struct sockaddr_in6 *)a;
sin6_b = (struct sockaddr_in6 *)b;
if (!memcmp(&sin6_a->sin6_addr, &sin6_b->sin6_addr,
sizeof(struct in6_addr)))
return 1;
break;
}
return 0;
}
/* determine if the given address is a local address */
int osmo_sockaddr_is_local(struct sockaddr *addr, socklen_t addrlen)
{
struct ifaddrs *ifaddr, *ifa;
if (getifaddrs(&ifaddr) == -1) {
perror("getifaddrs");
return -EIO;
}
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (sockaddr_equal(ifa->ifa_addr, addr, addrlen))
return 1;
}
return 0;
}