[PATCH 2/3] libabis: add VTY commands to route IPA flows

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/OpenBSC@lists.osmocom.org/.

pablo at gnumonks.org pablo at gnumonks.org
Tue Apr 19 17:47:46 UTC 2011


From: Pablo Neira Ayuso <pablo at gnumonks.org>

This patch adds VTY commands to route IPA flows. This works as it
follows:

$ telnet localhost 4242
%% enter ENABLE mode
> enable
%% create an input instance that binds to 192.168.1.1:9999
# ipa instance input bind 192.168.1.1 tcp port 9999
%% create an output instance that connects to 192.168.1.2:8888
# ipa instance output connect 192.168.1.2 tcp port 8888
%% create a route between the input and output instance, routing messages
whose streamid is 0xff (OML).
# ipa route instance input streamid 0xff instance output streamid 0xff

We also allow mangling the streamid in case that the source and the
destination streamids are not the same.

This patch includes the following commands:

%% to delete one instance
# ipa no instance input

%% to delete one route
# ipa no route instance input streamid 0xff instance output streamid 0xff
---
 openbsc/include/openbsc/ipaccess.h   |    2 +
 openbsc/include/openbsc/vty.h        |    1 +
 openbsc/src/ipaccess/Makefile.am     |    4 +-
 openbsc/src/libabis/Makefile.am      |    2 +-
 openbsc/src/libabis/input/ipaccess.c |  891 ++++++++++++++++++++++++++++++++++
 openbsc/src/libbsc/bsc_vty.c         |    1 +
 openbsc/src/openbsc.cfg.ipa-proxy    |   18 +
 openbsc/tests/db/Makefile.am         |    5 +-
 8 files changed, 919 insertions(+), 5 deletions(-)
 create mode 100644 openbsc/src/openbsc.cfg.ipa-proxy

diff --git a/openbsc/include/openbsc/ipaccess.h b/openbsc/include/openbsc/ipaccess.h
index d965aea..28ec49a 100644
--- a/openbsc/include/openbsc/ipaccess.h
+++ b/openbsc/include/openbsc/ipaccess.h
@@ -86,6 +86,8 @@ int ipaccess_parse_unitid(const char *str, uint16_t *site_id, uint16_t *bts_id,
 int ipaccess_drop_oml(struct gsm_bts *bts);
 int ipaccess_drop_rsl(struct gsm_bts_trx *trx);
 
+void ipaccess_vty_init(void);
+
 /*
  * Firmware specific header
  */
diff --git a/openbsc/include/openbsc/vty.h b/openbsc/include/openbsc/vty.h
index ded2e15..56950b5 100644
--- a/openbsc/include/openbsc/vty.h
+++ b/openbsc/include/openbsc/vty.h
@@ -35,6 +35,7 @@ enum bsc_vty_node {
 	MSC_NODE,
 	OM2K_NODE,
 	TRUNK_NODE,
+	IPA_NODE,
 };
 
 extern int bsc_vty_is_config_node(struct vty *vty, int node);
diff --git a/openbsc/src/ipaccess/Makefile.am b/openbsc/src/ipaccess/Makefile.am
index 4fe1e37..2ee30e3 100644
--- a/openbsc/src/ipaccess/Makefile.am
+++ b/openbsc/src/ipaccess/Makefile.am
@@ -1,6 +1,6 @@
 INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
-AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(COVERAGE_CFLAGS)
-AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(COVERAGE_LDFLAGS)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS)
 
 bin_PROGRAMS = ipaccess-find ipaccess-config ipaccess-proxy
 
diff --git a/openbsc/src/libabis/Makefile.am b/openbsc/src/libabis/Makefile.am
index ffaa201..d340a3f 100644
--- a/openbsc/src/libabis/Makefile.am
+++ b/openbsc/src/libabis/Makefile.am
@@ -1,6 +1,6 @@
 INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
 AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
-AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(COVERAGE_LDFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS)
 
 noinst_LIBRARIES = libabis.a
 
diff --git a/openbsc/src/libabis/input/ipaccess.c b/openbsc/src/libabis/input/ipaccess.c
index d2572fb..0cd3b2d 100644
--- a/openbsc/src/libabis/input/ipaccess.c
+++ b/openbsc/src/libabis/input/ipaccess.c
@@ -31,6 +31,7 @@
 #include <sys/socket.h>
 #include <sys/ioctl.h>
 #include <arpa/inet.h>
+#include <stdbool.h>
 
 #include <osmocom/core/select.h>
 #include <osmocom/gsm/tlv.h>
@@ -45,6 +46,8 @@
 #include <openbsc/ipaccess.h>
 #include <openbsc/socket.h>
 #include <openbsc/signal.h>
+#include <openbsc/vty.h>
+#include <openbsc/socket.h>
 
 #define PRIV_OML 1
 #define PRIV_RSL 2
@@ -808,6 +811,894 @@ int ipaccess_connect(struct e1inp_line *line, struct sockaddr_in *sa)
 	//return e1inp_line_register(line);
 }
 
+/*
+ * data structures used by the IPA VTY commands
+ */
+static LLIST_HEAD(ipa_instance_list);
+
+enum ipa_instance_net_type {
+	IPA_INSTANCE_T_NONE,
+	IPA_INSTANCE_T_BIND,
+	IPA_INSTANCE_T_CONNECT,
+	IPA_INSTANCE_T_MAX
+};
+
+struct ipa_instance_net {
+	struct in_addr			addr;
+	uint16_t			port;
+	enum ipa_instance_net_type	type;
+};
+
+struct ipa_instance {
+	struct llist_head		head;
+#define IPA_INSTANCE_NAME		16
+	char				name[IPA_INSTANCE_NAME];
+	struct ipa_instance_net		net;
+	int				refcnt;
+};
+
+static LLIST_HEAD(ipa_route_list);
+
+/* Several routes pointing to the same instances share this. */
+struct ipa_route_shared {
+	int					refcnt;
+
+	/* this file descriptor is used to accept() new connections. */
+	struct bsc_fd				bfd;
+
+	struct {
+		struct ipa_instance		*inst;
+		struct bitvec			streamid_map;
+		uint8_t				streamid_map_data[(0xff+1)/8];
+		uint8_t				streamid[0xff];
+	} src;
+	struct {
+		struct ipa_instance		*inst;
+		struct bitvec			streamid_map;
+		uint8_t				streamid_map_data[(0xff+1)/8];
+		uint8_t				streamid[0xff];
+	} dst;
+
+	struct llist_head			conn_list;
+};
+
+/* One route is composed of two instances. */
+struct ipa_route {
+	struct llist_head			head;
+
+	struct {
+		uint8_t				streamid;
+	} src;
+	struct {
+		uint8_t				streamid;
+	} dst;
+
+	struct ipa_route_shared			*shared;
+};
+
+enum ipa_conn_state {
+	IPA_CONN_S_NONE,
+	IPA_CONN_S_CONNECTING,
+	IPA_CONN_S_CONNECTED,
+	IPA_CONN_S_MAX
+};
+
+/* One route may forward more than one connection. */
+struct ipa_conn {
+	struct llist_head		head;
+
+	struct {
+		/* this is the file descriptor that accept() returns. */
+		struct bsc_fd		bfd;
+		struct llist_head	tx_queue;
+		/* special handling for segmented message. */
+		ssize_t			remaining_bytes;
+	} src;
+	struct {
+		/* this is the file descriptor that connect() returns. */
+		struct bsc_fd		bfd;
+		struct llist_head	tx_queue;
+		/* special handling for segmented message. */
+		ssize_t			remaining_bytes;
+		enum ipa_conn_state	state;
+		struct timer_list	reconnect_timer;
+	} dst;
+	struct ipa_route		*route;
+};
+
+/*
+ * socket callbacks used by IPA VTY commands
+ */
+
+/* try to reconnect after 5 seconds, make this configurable in the future. */
+#define IPA_TIMER_RECONNECT	5
+
+/* if we use TS1_ALLOC_SIZE, which is 900 bytes, we may truncate messages. */
+#define IPA_PROXY_ALLOC_SIZE	1200
+
+static void ipa_reconnect_timer_cb(void *data);
+
+static void ipa_sock_dst_reconnect(struct bsc_fd *bfd, struct ipa_conn *conn)
+{
+	if (bsc_timer_pending(&conn->dst.reconnect_timer)) {
+		LOGP(DINP, LOGL_ERROR, "Report bug, this SHOULD NOT happen "
+					"reconnection already scheduled\n");
+		return;
+	}
+	bsc_unregister_fd(&conn->dst.bfd);
+	close(conn->dst.bfd.fd);
+	conn->dst.bfd.fd = -1;
+
+	conn->dst.reconnect_timer.cb = ipa_reconnect_timer_cb;
+	conn->dst.reconnect_timer.data = conn;
+	bsc_schedule_timer(&conn->dst.reconnect_timer, IPA_TIMER_RECONNECT, 0);
+	LOGP(DINP, LOGL_NOTICE, "I will try to reconnect to dst "
+			"IPA peer in %d seconds\n", IPA_TIMER_RECONNECT);
+}
+
+static int ipa_sock_dst_read(struct bsc_fd *connect_bfd, struct ipa_conn *conn)
+{
+	struct ipaccess_head *hh;
+	struct msgb *msg;
+	int ret;
+
+	msg = msgb_alloc(IPA_PROXY_ALLOC_SIZE, "IPA");
+	if (msg == NULL) {
+		LOGP(DINP, LOGL_ERROR, "failed to alloc msg\n");
+		return -ENOMEM;
+	}
+	ret = recv(connect_bfd->fd, msg->data,
+					msg->data_len, 0);
+	if (ret < 0) {
+		if (errno == EPIPE || errno == ENOTCONN) {
+			LOGP(DINP, LOGL_NOTICE,
+			     "destination IPA peer has closed "
+			     "the connection\n");
+		} else {
+			LOGP(DINP, LOGL_ERROR, "failed to "
+			     "recv from destination IPA peer, "
+			     "reason=`%s'\n", strerror(errno));
+		}
+		conn->dst.state = IPA_CONN_S_CONNECTING;
+		ipa_sock_dst_reconnect(connect_bfd, conn);
+		msgb_free(msg);
+		return ret;
+	} else if (ret == 0) {
+		LOGP(DINP, LOGL_NOTICE, "destination IPA peer "
+		     "has closed the connection\n");
+		conn->dst.state = IPA_CONN_S_CONNECTING;
+		ipa_sock_dst_reconnect(connect_bfd, conn);
+		msgb_free(msg);
+		return 0;
+	}
+	/* remaining bytes of a segmented message. */
+	if (conn->dst.remaining_bytes > 0) {
+		conn->dst.remaining_bytes -= ret;
+		msgb_put(msg, ret);
+		msgb_enqueue(&conn->src.tx_queue, msg);
+		conn->src.bfd.when |= BSC_FD_WRITE;
+		return 0;
+	}
+	if (ret < sizeof(struct ipaccess_head)) {
+		LOGP(DINP, LOGL_ERROR, "received too small "
+			"message from destination IPA\n");
+		msgb_free(msg);
+		return -EINVAL;
+	}
+	msgb_put(msg, ret);
+	hh = (struct ipaccess_head *)msg->data;
+	/* check if we have a route for this message. */
+	if (bitvec_get_bit_pos(
+	     &conn->route->shared->dst.streamid_map,
+	     hh->proto) != ONE) {
+		LOGP(DINP, LOGL_NOTICE, "we don't have a "
+			"route for streamid 0x%x\n", hh->proto);
+		msgb_free(msg);
+		return 0;
+	}
+	/* segmented message, expect remaining bytes. */
+	if (ret < ntohs(hh->len)) {
+		conn->dst.remaining_bytes =
+			ntohs(hh->len) + sizeof(*hh) - ret;
+	}
+	/* mangle message, if required. */
+	hh->proto = conn->route->shared->src.streamid[hh->proto];
+	msgb_enqueue(&conn->src.tx_queue, msg);
+	conn->src.bfd.when |= BSC_FD_WRITE;
+	return 0;
+}
+
+static int ipa_sock_dst_cb(struct bsc_fd *connect_bfd, unsigned int what)
+{
+	int ret = 0;
+	struct msgb *msg;
+	struct llist_head *lh;
+	struct ipa_conn *conn = connect_bfd->data;
+	int error;
+	size_t len = sizeof(error);
+
+	switch(conn->dst.state) {
+	case IPA_CONN_S_CONNECTING:
+		ret = getsockopt(connect_bfd->fd, SOL_SOCKET, SO_ERROR,
+				 &error, &len);
+		if (ret >= 0 && error > 0) {
+			conn->dst.state = IPA_CONN_S_CONNECTING;
+			ipa_sock_dst_reconnect(connect_bfd, conn);
+			return 0;
+		}
+		conn->dst.state = IPA_CONN_S_CONNECTED;
+		LOGP(DINP, LOGL_NOTICE, "connected to destination IPA peer!\n");
+		break;
+	case IPA_CONN_S_CONNECTED:
+		/* receive messages: destination -> source. */
+		if (what & BSC_FD_READ)
+			ipa_sock_dst_read(connect_bfd, conn);
+
+		/* send messages: source -> destination. */
+		if (what & BSC_FD_WRITE) {
+			if (llist_empty(&conn->dst.tx_queue)) {
+				connect_bfd->when &= ~BSC_FD_WRITE;
+				return 0;
+			}
+			lh = conn->dst.tx_queue.next;
+			llist_del(lh);
+			msg = llist_entry(lh, struct msgb, list);
+
+			ret = send(conn->dst.bfd.fd, msg->data, msg->len, 0);
+			if (ret < 0) {
+				if (errno == EPIPE || errno == ENOTCONN) {
+					conn->dst.state =
+						IPA_CONN_S_CONNECTING;
+					ipa_sock_dst_reconnect(connect_bfd,
+									conn);
+				}
+				LOGP(DINP, LOGL_ERROR, "failed to send (%s)\n",
+					strerror(errno));
+			}
+			msgb_free(msg);
+		}
+		break;
+	default:
+		LOGP(DINP, LOGL_ERROR, "unknown state type");
+		break;
+	}
+	return ret;
+}
+
+static void ipa_reconnect_timer_cb(void *data)
+{
+	struct ipa_conn *conn = data;
+	int ret;
+
+	ret = make_sock_stream_connect(&conn->dst.bfd, IPPROTO_TCP,
+				       conn->route->shared->dst.inst->net.addr,
+				       conn->route->shared->dst.inst->net.port,
+				       0, ipa_sock_dst_cb, conn);
+	if (ret < 0)
+		LOGP(DINP, LOGL_ERROR, "cannot create destination IPA socket");
+}
+
+static int ipa_sock_src_read(struct bsc_fd *bfd, struct ipa_conn *conn)
+{
+	int ret;
+	struct msgb *msg;
+	struct ipaccess_head *hh;
+
+	msg = msgb_alloc(IPA_PROXY_ALLOC_SIZE, "IPA");
+	if (msg == NULL) {
+		LOGP(DINP, LOGL_ERROR, "failed to msgb_alloc\n");
+		return -ENOMEM;
+	}
+	ret = recv(bfd->fd, msg->data, msg->data_len, 0);
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "failed to recv from origin "
+		     "IPA peer, reason=`%s'\n", strerror(errno));
+                bsc_unregister_fd(bfd);
+                close(bfd->fd);
+		bfd->fd = -1;
+		msgb_free(msg);
+		return ret;
+	} else if (ret == 0) {
+		LOGP(DINP, LOGL_NOTICE, "origin IPA peer has closed "
+					"the connection\n");
+                bsc_unregister_fd(bfd);
+                close(bfd->fd);
+		bfd->fd = -1;
+		bsc_del_timer(&conn->dst.reconnect_timer);
+		if (conn->dst.bfd.fd != -1) {
+			bsc_unregister_fd(&conn->dst.bfd);
+			close(conn->dst.bfd.fd);
+			conn->dst.bfd.fd = -1;
+		}
+		llist_del(&conn->head);
+		talloc_free(conn);
+		msgb_free(msg);
+		return 0;
+	}
+	/* remaining bytes of a segmented message. */
+	if (conn->src.remaining_bytes > 0) {
+		conn->src.remaining_bytes -= ret;
+		msgb_put(msg, ret);
+		msgb_enqueue(&conn->dst.tx_queue, msg);
+		conn->dst.bfd.when |= BSC_FD_WRITE;
+		return 0;
+	}
+	if (ret < sizeof(struct ipaccess_head)) {
+		LOGP(DINP, LOGL_ERROR, "received too small message "
+				       "from origin IPA\n");
+		msgb_free(msg);
+		return -EINVAL;
+	}
+	msgb_put(msg, ret);
+	hh = (struct ipaccess_head *)msg->data;
+	/* check if we have a route for this message. */
+	if (bitvec_get_bit_pos(&conn->route->shared->src.streamid_map,
+			hh->proto) != ONE) {
+		LOGP(DINP, LOGL_NOTICE, "we don't have a "
+			"route for streamid 0x%x\n", hh->proto);
+		msgb_free(msg);
+		return 0;
+	}
+	/* segmented message, expect remaining bytes. */
+	if (ret < ntohs(hh->len)) {
+		conn->src.remaining_bytes =
+			ntohs(hh->len) + sizeof(*hh) - ret;
+	}
+	/* mangle message, if required. */
+	hh->proto = conn->route->shared->dst.streamid[hh->proto];
+	msgb_enqueue(&conn->dst.tx_queue, msg);
+	conn->dst.bfd.when |= BSC_FD_WRITE;
+	return 0;
+}
+
+static int ipa_sock_src_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	struct ipa_conn *conn = bfd->data;
+	struct msgb *msg;
+	int ret = 0;
+
+	/* receive messages: source -> destination. */
+	if (what & BSC_FD_READ)
+		ipa_sock_src_read(bfd, conn);
+
+	/* send messages: destination -> source. */
+	if (what & BSC_FD_WRITE) {
+		struct llist_head *lh;
+
+		if (llist_empty(&conn->src.tx_queue)) {
+			bfd->when &= ~BSC_FD_WRITE;
+			return 0;
+		}
+		lh = conn->src.tx_queue.next;
+		llist_del(lh);
+		msg = llist_entry(lh, struct msgb, list);
+
+		ret = send(bfd->fd, msg->data, msg->len, 0);
+		if (ret < 0) {
+			LOGP(DINP, LOGL_ERROR, "failed to send (%s)\n",
+				strerror(errno));
+	                bsc_unregister_fd(bfd);
+	                close(bfd->fd);
+			bfd->fd = -1;
+			msgb_free(msg);
+		}
+	}
+	return ret;
+}
+
+static int
+ipa_sock_src_accept_cb(struct bsc_fd *listen_bfd, unsigned int what)
+{
+	int ret;
+	struct sockaddr_in sa;
+	struct ipa_route *route = listen_bfd->data;
+	socklen_t sa_len = sizeof(sa);
+	struct ipa_conn *conn;
+
+	ret = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len);
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "failed to accept from origin IPA "
+				"peer, reason=`%s'\n", strerror(errno));
+		return ret;
+	}
+	LOGP(DINP, LOGL_NOTICE, "accept()ed new link from origin IPA "
+			"peer: %s\n", inet_ntoa(sa.sin_addr));
+
+	conn = talloc_zero(tall_bsc_ctx, struct ipa_conn);
+	if (conn == NULL) {
+		LOGP(DINP, LOGL_ERROR, "cannot allocate memory for origin IPA "
+				       "peer: %s\n", inet_ntoa(sa.sin_addr));
+		close(ret);
+		return ret;
+	}
+	conn->route = route;
+	INIT_LLIST_HEAD(&conn->src.tx_queue);
+	INIT_LLIST_HEAD(&conn->dst.tx_queue);
+	conn->dst.state = IPA_CONN_S_CONNECTING;
+
+	conn->src.bfd.fd = ret;
+	conn->src.bfd.data = conn;
+	conn->src.bfd.cb = ipa_sock_src_cb;
+	conn->src.bfd.when = BSC_FD_READ;
+	ret = bsc_register_fd(&conn->src.bfd);
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not register FD\n");
+		close(conn->src.bfd.fd);
+		conn->src.bfd.fd = -1;
+		return ret;
+	}
+	ret = make_sock_stream_connect(&conn->dst.bfd, IPPROTO_TCP,
+				       route->shared->dst.inst->net.addr,
+				       route->shared->dst.inst->net.port,
+				       0, ipa_sock_dst_cb, conn);
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not create client: %s\n",
+			strerror(errno));
+		conn->dst.state = IPA_CONN_S_CONNECTING;
+		ipa_sock_dst_reconnect(&conn->dst.bfd, conn);
+	}
+	llist_add(&conn->head, &route->shared->conn_list);
+	return ret;
+}
+
+/*
+ * VTY commands for IPA
+ */
+DEFUN(ipa_proxy, ipa_cmd, "ipa", "Configure the ipaccess proxy")
+{
+	vty->index = NULL;
+	vty->node = IPA_NODE;
+	return CMD_SUCCESS;
+}
+
+static int __ipa_instance_add(struct vty *vty, int argc, const char *argv[])
+{
+	struct ipa_instance *ipi;
+	enum ipa_instance_net_type type;
+	struct in_addr addr;
+	uint16_t port;
+
+	if (argc < 4)
+		return CMD_ERR_INCOMPLETE;
+
+	llist_for_each_entry(ipi, &ipa_instance_list, head) {
+		if (strncmp(ipi->name, argv[0], IPA_INSTANCE_NAME) != 0)
+			continue;
+
+		vty_out(vty, "%% instance `%s' already exists%s",
+			ipi->name, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	if (strncmp(argv[1], "bind", IPA_INSTANCE_NAME) == 0)
+		type = IPA_INSTANCE_T_BIND;
+	else if (strncmp(argv[1], "connect", IPA_INSTANCE_NAME) == 0)
+		type = IPA_INSTANCE_T_CONNECT;
+	else
+		return CMD_ERR_INCOMPLETE;
+
+	if (inet_aton(argv[2], &addr) < 0) {
+		vty_out(vty, "%% invalid address %s%s", argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	port = atoi(argv[3]);
+
+	ipi = talloc_zero(tall_bsc_ctx, struct ipa_instance);
+	if (ipi == NULL) {
+		vty_out(vty, "%% can't allocate memory for new instance%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	strncpy(ipi->name, argv[0], IPA_INSTANCE_NAME);
+	ipi->net.type = type;
+	memcpy(&ipi->net.addr, &addr, sizeof(struct in_addr));
+	ipi->net.port = htons(port);
+	llist_add_tail(&ipi->head, &ipa_instance_list);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(ipa_instance_add, ipa_instance_add_cmd,
+      "ipa instance NAME (bind|connect) IP tcp port PORT",
+      "Bind or connect instance to address and port")
+{
+	return __ipa_instance_add(vty, argc, argv);
+}
+
+DEFUN(ipa_instance_del, ipa_instance_del_cmd,
+      "ipa no instance NAME",
+      "Delete instance to address and port")
+{
+	struct ipa_instance *ipi;
+
+	if (argc < 1)
+		return CMD_ERR_INCOMPLETE;
+
+	llist_for_each_entry(ipi, &ipa_instance_list, head) {
+		if (strncmp(ipi->name, argv[0], IPA_INSTANCE_NAME) != 0)
+			continue;
+
+		if (ipi->refcnt > 0) {
+			vty_out(vty, "%% instance `%s' is in use%s",
+			ipi->name, VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		llist_del(&ipi->head);
+		talloc_free(ipi);
+		return CMD_SUCCESS;
+	}
+	vty_out(vty, "%% instance `%s' does not exist%s",
+		ipi->name, VTY_NEWLINE);
+
+	return CMD_WARNING;
+}
+
+DEFUN(ipa_instance_show, ipa_instance_show_cmd,
+      "ipa instance show", "Show existing ipaccess proxy instances")
+{
+	struct ipa_instance *this;
+
+	llist_for_each_entry(this, &ipa_instance_list, head) {
+		vty_out(vty, "instance %s %s %s tcp port %u%s",
+			this->name, inet_ntoa(this->net.addr),
+			this->net.type == IPA_INSTANCE_T_BIND ?
+				"bind" : "connect",
+			ntohs(this->net.port), VTY_NEWLINE);
+	}
+	return CMD_SUCCESS;
+}
+
+static int __ipa_route_add(struct vty *vty, int argc, const char *argv[])
+{
+	struct ipa_instance *ipi = vty->index;
+	struct ipa_instance *src = NULL, *dst = NULL;
+	uint32_t src_streamid, dst_streamid;
+	struct ipa_route *route, *matching_route = NULL;
+	struct ipa_route_shared *shared = NULL;
+	int ret;
+
+	if (argc < 4)
+		return CMD_ERR_INCOMPLETE;
+
+	llist_for_each_entry(ipi, &ipa_instance_list, head) {
+		if (strncmp(ipi->name, argv[0], IPA_INSTANCE_NAME) == 0) {
+			src = ipi;
+			continue;
+		}
+		if (strncmp(ipi->name, argv[2], IPA_INSTANCE_NAME) == 0) {
+			dst = ipi;
+			continue;
+		}
+	}
+	if (src == NULL) {
+		vty_out(vty, "%% instance `%s' does not exists%s",
+			argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	if (dst == NULL) {
+		vty_out(vty, "%% instance `%s' does not exists%s",
+			argv[2], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	if (src->net.type != IPA_INSTANCE_T_BIND) {
+		vty_out(vty, "%% instance `%s' is not of bind type%s",
+			argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	if (dst->net.type != IPA_INSTANCE_T_CONNECT) {
+		vty_out(vty, "%% instance `%s' is not of connect type%s",
+			argv[2], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	src_streamid = strtoul(argv[1], NULL, 16);
+	if (src_streamid > 0xff) {
+		vty_out(vty, "%% source streamid must be "
+			     ">= 0x00 and <= 0xff%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	dst_streamid = strtoul(argv[3], NULL, 16);
+	if (dst_streamid > 0xff) {
+		vty_out(vty, "%% destination streamid must be "
+			     ">= 0x00 and <= 0xff%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	llist_for_each_entry(route, &ipa_route_list, head) {
+		if (route->shared->src.inst == src &&
+		    route->shared->dst.inst == dst) {
+			if (route->src.streamid == src_streamid &&
+			    route->dst.streamid == dst_streamid) {
+				vty_out(vty, "%% this route already exists%s",
+					VTY_NEWLINE);
+				return CMD_WARNING;
+			}
+			matching_route = route;
+			break;
+		}
+	}
+	/* new route for this configuration. */
+	route = talloc_zero(tall_bsc_ctx, struct ipa_route);
+	if (route == NULL) {
+		vty_out(vty, "%% can't allocate memory for new route%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	route->src.streamid = src_streamid;
+	route->dst.streamid = dst_streamid;
+
+	if (matching_route != NULL) {
+		/* there's already a master route for these configuration. */
+		if (matching_route->shared->src.inst != src) {
+			vty_out(vty, "%% route does not contain "
+				     "source instance `%s'%s",
+				     argv[0], VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		if (matching_route->shared->dst.inst != dst) {
+			vty_out(vty, "%% route does not contain "
+				     "destination instance `%s'%s",
+				     argv[2], VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		/* use already existing shared routing information. */
+		shared = matching_route->shared;
+	} else {
+		/* this is a brand new route, allocate shared routing info. */
+		shared = talloc_zero(tall_bsc_ctx, struct ipa_route_shared);
+		if (shared == NULL) {
+			vty_out(vty, "%% can't allocate memory for "
+				"new route shared%s", VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		shared->src.streamid_map.data_len =
+			sizeof(shared->src.streamid_map_data);
+		shared->src.streamid_map.data =
+			shared->src.streamid_map_data;
+		shared->dst.streamid_map.data_len =
+			sizeof(shared->dst.streamid_map_data);
+		shared->dst.streamid_map.data =
+			shared->dst.streamid_map_data;
+
+		ret = make_sock(&shared->bfd, IPPROTO_TCP, INADDR_ANY,
+				ntohs(src->net.port), 0,
+				ipa_sock_src_accept_cb, route);
+		if (ret < 0) {
+			vty_out(vty, "%% can't bind instance `%s' to port%s",
+				src->name, VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		INIT_LLIST_HEAD(&shared->conn_list);
+	}
+	route->shared = shared;
+	src->refcnt++;
+	route->shared->src.inst = src;
+	dst->refcnt++;
+	route->shared->dst.inst = dst;
+	shared->src.streamid[src_streamid] = dst_streamid;
+	shared->dst.streamid[dst_streamid] = src_streamid;
+	ret = bitvec_set_bit_pos(&shared->src.streamid_map, src_streamid, ONE);
+	if (ret < 0) {
+		vty_out(vty, "%% bad bitmask (?)%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	ret = bitvec_set_bit_pos(&shared->dst.streamid_map, dst_streamid, ONE);
+	if (ret < 0) {
+		vty_out(vty, "%% bad bitmask (?)%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	shared->refcnt++;
+
+	llist_add_tail(&route->head, &ipa_route_list);
+	return CMD_SUCCESS;
+}
+
+DEFUN(ipa_route_add, ipa_route_add_cmd,
+      "ipa route instance NAME streamid HEXNUM "
+		"instance NAME streamid HEXNUM", "Add IPA route")
+{
+	return __ipa_route_add(vty, argc, argv);
+}
+
+DEFUN(ipa_route_del, ipa_route_del_cmd,
+      "ipa no route instance NAME streamid HEXNUM "
+		   "instance NAME streamid HEXNUM", "Delete IPA route")
+{
+	struct ipa_instance *ipi = vty->index;
+	struct ipa_instance *src = NULL, *dst = NULL;
+	uint32_t src_streamid, dst_streamid;
+	struct ipa_route *route, *matching_route = NULL;
+	struct ipa_conn *conn, *tmp;
+
+	if (argc < 4)
+		return CMD_ERR_INCOMPLETE;
+
+	llist_for_each_entry(ipi, &ipa_instance_list, head) {
+		if (strncmp(ipi->name, argv[0], IPA_INSTANCE_NAME) == 0) {
+			src = ipi;
+			continue;
+		}
+		if (strncmp(ipi->name, argv[2], IPA_INSTANCE_NAME) == 0) {
+			dst = ipi;
+			continue;
+		}
+	}
+	if (src == NULL) {
+		vty_out(vty, "%% instance `%s' does not exists%s",
+			argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	if (dst == NULL) {
+		vty_out(vty, "%% instance `%s' does not exists%s",
+			argv[2], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	if (src->net.type != IPA_INSTANCE_T_BIND) {
+		vty_out(vty, "%% instance `%s' is not of bind type%s",
+			argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	if (dst->net.type != IPA_INSTANCE_T_CONNECT) {
+		vty_out(vty, "%% instance `%s' is not of connect type%s",
+			argv[2], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	src_streamid = strtoul(argv[1], NULL, 16);
+	if (src_streamid > 0xff) {
+		vty_out(vty, "%% source streamid must be "
+			     ">= 0x00 and <= 0xff%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	dst_streamid = strtoul(argv[3], NULL, 16);
+	if (dst_streamid > 0xff) {
+		vty_out(vty, "%% destination streamid must be "
+			     ">= 0x00 and <= 0xff%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	llist_for_each_entry(route, &ipa_route_list, head) {
+		if (route->shared->src.inst == src &&
+		    route->shared->dst.inst == dst &&
+		    route->src.streamid == src_streamid &&
+		    route->dst.streamid == dst_streamid) {
+			matching_route = route;
+			break;
+		}
+	}
+	if (matching_route == NULL) {
+		vty_out(vty, "%% no route with that configuration%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	/* delete this route from list. */
+	llist_del(&matching_route->head);
+
+	if (--matching_route->shared->refcnt == 0) {
+		/* nobody else using this route, release all resources. */
+		llist_for_each_entry_safe(conn, tmp,
+				&matching_route->shared->conn_list, head) {
+			bsc_unregister_fd(&conn->src.bfd);
+			close(conn->src.bfd.fd);
+			conn->src.bfd.fd = -1;
+			bsc_del_timer(&conn->dst.reconnect_timer);
+			if (conn->dst.bfd.fd != -1) {
+				bsc_unregister_fd(&conn->dst.bfd);
+				close(conn->dst.bfd.fd);
+				conn->dst.bfd.fd = -1;
+			}
+			llist_del(&conn->head);
+			talloc_free(conn);
+		}
+		bsc_unregister_fd(&route->shared->bfd);
+		close(route->shared->bfd.fd);
+		route->shared->bfd.fd = -1;
+
+		talloc_free(route->shared);
+	} else {
+		/* otherwise, revert the mapping that this route applies. */
+		bitvec_set_bit_pos(&matching_route->shared->src.streamid_map,
+				   src_streamid, ZERO);
+		bitvec_set_bit_pos(&matching_route->shared->dst.streamid_map,
+				   dst_streamid, ZERO);
+		matching_route->shared->src.streamid[src_streamid] = 0x00;
+		matching_route->shared->dst.streamid[dst_streamid] = 0x00;
+	}
+	matching_route->shared->src.inst->refcnt--;
+	matching_route->shared->dst.inst->refcnt--;
+	talloc_free(matching_route);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(ipa_route_show, ipa_route_show_cmd,
+      "ipa route show", "Show existing ipaccess proxy routes")
+{
+	struct ipa_route *this;
+
+	llist_for_each_entry(this, &ipa_route_list, head) {
+		vty_out(vty, "route instance %s streamid 0x%.2x "
+			           "instance %s streamid 0x%.2x%s",
+			this->shared->src.inst->name, this->src.streamid,
+			this->shared->dst.inst->name, this->dst.streamid,
+			VTY_NEWLINE);
+	}
+	return CMD_SUCCESS;
+}
+
+/*
+ * Config for ipaccess-proxy
+ */
+DEFUN(ipa_cfg, ipa_cfg_cmd, "ipa", "Configure the ipaccess proxy")
+{
+	vty->index = NULL;
+	vty->node = IPA_NODE;
+	return CMD_SUCCESS;
+}
+
+/* all these below look like enable commands, but without the ipa prefix. */
+DEFUN(ipa_route_cfg_add, ipa_route_cfg_add_cmd,
+      "route instance NAME streamid HEXNUM "
+	    "instance NAME streamid HEXNUM", "Add IPA route")
+{
+	return __ipa_route_add(vty, argc, argv);
+}
+
+DEFUN(ipa_instance_cfg_add, ipa_instance_cfg_add_cmd,
+      "instance NAME (bind|connect) IP tcp port PORT",
+      "Bind or connect instance to address and port")
+{
+	return __ipa_instance_add(vty, argc, argv);
+}
+
+struct cmd_node ipa_node = {
+	IPA_NODE,
+	"%s(ipa)#",
+	1,
+};
+
+static int ipa_cfg_write(struct vty *vty)
+{
+	bool heading = false;
+	struct ipa_instance *inst;
+	struct ipa_route *route;
+
+	llist_for_each_entry(inst, &ipa_instance_list, head) {
+		if (!heading) {
+			vty_out(vty, "ipa%s", VTY_NEWLINE);
+			heading = true;
+		}
+		vty_out(vty, " instance %s %s %s tcp port %u%s",
+			inst->name,
+			inst->net.type == IPA_INSTANCE_T_BIND ?
+				"bind" : "connect",
+			inet_ntoa(inst->net.addr),
+			ntohs(inst->net.port), VTY_NEWLINE);
+	}
+	llist_for_each_entry(route, &ipa_route_list, head) {
+		vty_out(vty, " route instance %s streamid 0x%.2x "
+				    "instance %s streamid 0x%.2x%s",
+			route->shared->src.inst->name, route->src.streamid,
+			route->shared->dst.inst->name, route->dst.streamid,
+			VTY_NEWLINE);
+	}
+	return CMD_SUCCESS;
+}
+
+void ipaccess_vty_init(void)
+{
+	install_element(ENABLE_NODE, &ipa_cmd);
+	install_element(ENABLE_NODE, &ipa_instance_add_cmd);
+	install_element(ENABLE_NODE, &ipa_instance_del_cmd);
+	install_element(ENABLE_NODE, &ipa_instance_show_cmd);
+	install_element(ENABLE_NODE, &ipa_route_add_cmd);
+	install_element(ENABLE_NODE, &ipa_route_del_cmd);
+	install_element(ENABLE_NODE, &ipa_route_show_cmd);
+
+	install_element(CONFIG_NODE, &ipa_cfg_cmd);
+	install_node(&ipa_node, ipa_cfg_write);
+	install_default(IPA_NODE);
+	install_element(IPA_NODE, &ournode_exit_cmd);
+	install_element(IPA_NODE, &ournode_end_cmd);
+	install_element(IPA_NODE, &ipa_instance_cfg_add_cmd);
+	install_element(IPA_NODE, &ipa_route_cfg_add_cmd);
+}
+
 int ipaccess_setup(struct gsm_network *gsmnet)
 {
 	int ret;
diff --git a/openbsc/src/libbsc/bsc_vty.c b/openbsc/src/libbsc/bsc_vty.c
index e052bda..9c1066d 100644
--- a/openbsc/src/libbsc/bsc_vty.c
+++ b/openbsc/src/libbsc/bsc_vty.c
@@ -2791,6 +2791,7 @@ int bsc_vty_init(const struct log_info *cat)
 	abis_nm_vty_init();
 	abis_om2k_vty_init();
 	e1inp_vty_init();
+	ipaccess_vty_init();
 
 	bsc_vty_init_extra();
 
diff --git a/openbsc/src/openbsc.cfg.ipa-proxy b/openbsc/src/openbsc.cfg.ipa-proxy
new file mode 100644
index 0000000..30615da
--- /dev/null
+++ b/openbsc/src/openbsc.cfg.ipa-proxy
@@ -0,0 +1,18 @@
+!
+! Example file to setup an IPA proxy between ip.access nanoBTS and OpenBSC
+!
+password foo
+!
+line vty
+ no login
+!
+ipa
+ instance in0 bind 192.168.0.1 tcp port 3002
+ instance out0 connect 192.168.0.2 tcp port 3002
+ instance in1 bind 192.168.0.1 tcp port 3003
+ instance out1 connect 192.168.0.2 tcp port 3003
+ route instance in0 streamid 0xff instance out0 streamid 0xff
+ route instance in0 streamid 0xfe instance out0 streamid 0xfe
+ route instance in1 streamid 0x00 instance out1 streamid 0x00
+ route instance in1 streamid 0xfe instance out1 streamid 0xfe
+end
diff --git a/openbsc/tests/db/Makefile.am b/openbsc/tests/db/Makefile.am
index 98fdccc..2a21c0d 100644
--- a/openbsc/tests/db/Makefile.am
+++ b/openbsc/tests/db/Makefile.am
@@ -1,5 +1,5 @@
 INCLUDES = $(all_includes) -I$(top_srcdir)/include
-AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(COVERAGE_CFLAGS)
+AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(COVERAGE_CFLAGS)
 AM_LDFLAGS = $(COVERAGE_LDFLAGS)
 
 noinst_PROGRAMS = db_test
@@ -11,5 +11,6 @@ db_test_LDADD =	$(top_builddir)/src/libbsc/libbsc.a \
 		$(top_builddir)/src/libabis/libabis.a \
 		$(top_builddir)/src/libtrau/libtrau.a \
 		$(top_builddir)/src/libcommon/libcommon.a \
-		$(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) -ldl -ldbi
+		$(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) \
+		-ldl -ldbi
 
-- 
1.7.2.3





More information about the OpenBSC mailing list