diff mbox

Add support of virtual networks for sending network name to ms depending on imsi

Message ID 1460968301-8162-1-git-send-email-kluchnikovi@gmail.com
State Not Applicable
Headers show

Commit Message

Ivan Kluchnikov April 18, 2016, 8:31 a.m. UTC
Before sending mm info message to ms:
- try to find virtual network with the imsi prefix which matches subscriber's imsi
- if virtual network was found, use long and short network names of this virtual network for subscriber
- if virtual network was not found, use long and short network names of main network for subscriber
---
 openbsc/include/openbsc/gsm_data.h |  16 +++++
 openbsc/include/openbsc/vty.h      |   2 +
 openbsc/src/libbsc/bsc_vty.c       | 138 +++++++++++++++++++++++++++++++++++++
 openbsc/src/libbsc/net_init.c      |   1 +
 openbsc/src/libcommon/common_vty.c |  19 +++++
 openbsc/src/libcommon/gsm_data.c   |  41 +++++++++++
 openbsc/src/libmsc/gsm_04_08.c     |  42 ++++++++---
 openbsc/src/libmsc/smpp_vty.c      |  10 ---
 openbsc/tests/vty_test_runner.py   |  40 +++++++++++
 9 files changed, 291 insertions(+), 18 deletions(-)

Comments

Harald Welte April 18, 2016, 9:38 a.m. UTC | #1
Hi Ivan,

I would prefer if we could use long-name and short-name instead of 'long
name' and 'short name' in the VTY syntax.

The VTY commands with spaces in them are a sign of my lack of
understanding of the VTY system at that time.  Multiple words should
only be used if there actually is a 'fork' in the tree, i.e. if several
commands are possible.

So two commands 'name short' and 'name long' would make sense, as they
are about name, and there are two options, one for short, and one for
long name.  Basically the same as 'name (short|long)'

But 'short name' and 'long name' will install command nodes for 'short'
and long', which doesn't make sense.  We keep the old syntax for
compatibility on the 'network' node, but for new code added, we should
try to avoid this.

So let's either use 'name (short|long)' (maybe the best solutioin) or
'short-name' and 'long-name'.

> +#define IMSI_PREFIX_LENGTH 16

we should probably not pull the magic number 16 out of our sleeve here,
but try to use an existing #define for the length of an IMSI.  I'm quite
sure we have one somewhere in libosmocore or the openbsc code base.

> +struct gsm_virt_network *gsm_virt_net_alloc_register(struct gsm_network *net)
> +{
> +	struct gsm_virt_network *virt_net;
> +
> +	virt_net = gsm_virt_net_alloc(net);
> +	if (!virt_net)
> +		return NULL;
> +
> +	virt_net->nr = net->num_virt_net++;
> +	virt_net->network = net;
> +	strcpy(virt_net->imsi_prefix, "00101");
> +	virt_net->name_short = talloc_strdup(net, "OpenBSC");
> +	virt_net->name_long = talloc_strdup(net, "OpenBSC");

why do we have hard-coded defaults for a prefix 00101 inside the code?
I think we should advoid such automatism, as creating a new virtual
network will then instantaneously match 00101 prefixed IMSIs without the
user ever specifying this. Or am I misunderstanding this?

Thanks!
Ivan Kluchnikov April 18, 2016, 5:22 p.m. UTC | #2
Hi Harald,

I would prefer if we could use long-name and short-name instead of 'long
> name' and 'short name' in the VTY syntax.
>
> The VTY commands with spaces in them are a sign of my lack of
> understanding of the VTY system at that time.  Multiple words should
> only be used if there actually is a 'fork' in the tree, i.e. if several
> commands are possible.
>
> So two commands 'name short' and 'name long' would make sense, as they
> are about name, and there are two options, one for short, and one for
> long name.  Basically the same as 'name (short|long)'
>
> But 'short name' and 'long name' will install command nodes for 'short'
> and long', which doesn't make sense.  We keep the old syntax for
> compatibility on the 'network' node, but for new code added, we should
> try to avoid this.
>
> So let's either use 'name (short|long)' (maybe the best solutioin) or
> 'short-name' and 'long-name'.
>

Thank you for explanation.
We will have different commands for configuring the same parameters for
main network and virtual networks ("short name OpenBSC" and "name short
OpenBSC"), but I think it's ok, I will change it to 'name (short|long)'.


> > +#define IMSI_PREFIX_LENGTH 16
>
> we should probably not pull the magic number 16 out of our sleeve here,
> but try to use an existing #define for the length of an IMSI.  I'm quite
> sure we have one somewhere in libosmocore or the openbsc code base.
>

Ok, I see that GSM_IMSI_LENGTH is defined in gsm_subscriber.h, so I can
just move it to gsm_data.h and use it.
Btw, as I know maximum imsi length is 15, why 17 is used for
GSM_IMSI_LENGTH instead of 16?


> > +struct gsm_virt_network *gsm_virt_net_alloc_register(struct gsm_network
> *net)
> > +{
> > +     struct gsm_virt_network *virt_net;
> > +
> > +     virt_net = gsm_virt_net_alloc(net);
> > +     if (!virt_net)
> > +             return NULL;
> > +
> > +     virt_net->nr = net->num_virt_net++;
> > +     virt_net->network = net;
> > +     strcpy(virt_net->imsi_prefix, "00101");
> > +     virt_net->name_short = talloc_strdup(net, "OpenBSC");
> > +     virt_net->name_long = talloc_strdup(net, "OpenBSC");
>
> why do we have hard-coded defaults for a prefix 00101 inside the code?
> I think we should advoid such automatism, as creating a new virtual
> network will then instantaneously match 00101 prefixed IMSIs without the
> user ever specifying this. Or am I misunderstanding this?
>

Yes, you are right, by default imsi-prefix should be undefined, I will fix
it.
diff mbox

Patch

diff --git a/openbsc/include/openbsc/gsm_data.h b/openbsc/include/openbsc/gsm_data.h
index 6d7aba3..b3e9603 100644
--- a/openbsc/include/openbsc/gsm_data.h
+++ b/openbsc/include/openbsc/gsm_data.h
@@ -248,6 +248,9 @@  struct gsm_network {
 	unsigned int num_bts;
 	struct llist_head bts_list;
 
+	unsigned int num_virt_net;
+	struct llist_head virt_net_list;
+
 	/* timer values */
 	int T3101;
 	int T3103;
@@ -293,6 +296,16 @@  struct gsm_network {
 	struct ctrl_handle *ctrl;
 };
 
+#define IMSI_PREFIX_LENGTH 16
+struct gsm_virt_network {
+	struct llist_head list;
+	uint8_t nr;
+	struct gsm_network *network;
+	char imsi_prefix[IMSI_PREFIX_LENGTH];
+	char *name_long;
+	char *name_short;
+};
+
 struct osmo_esme;
 
 enum gsm_sms_source_id {
@@ -435,6 +448,9 @@  int gsm_bts_model_register(struct gsm_bts_model *model);
 struct gsm_subscriber_connection *subscr_con_allocate(struct gsm_lchan *lchan);
 void subscr_con_free(struct gsm_subscriber_connection *conn);
 
+struct gsm_virt_network *gsm_virt_net_alloc_register(struct gsm_network *net);
+struct gsm_virt_network *gsm_virt_net_num(struct gsm_network *net, int num);
+
 struct gsm_bts *gsm_bts_alloc_register(struct gsm_network *net,
 					enum gsm_bts_type type,
 					uint8_t bsic);
diff --git a/openbsc/include/openbsc/vty.h b/openbsc/include/openbsc/vty.h
index bc30e23..711f82a 100644
--- a/openbsc/include/openbsc/vty.h
+++ b/openbsc/include/openbsc/vty.h
@@ -17,6 +17,7 @@  extern struct cmd_element cfg_no_description_cmd;
 
 enum bsc_vty_node {
 	GSMNET_NODE = _LAST_OSMOVTY_NODE + 1,
+	VIRT_NET_NODE,
 	BTS_NODE,
 	TRX_NODE,
 	TS_NODE,
@@ -41,6 +42,7 @@  enum bsc_vty_node {
 
 extern int bsc_vty_is_config_node(struct vty *vty, int node);
 extern void bsc_replace_string(void *ctx, char **dst, const char *newstr);
+extern int osmo_is_digits(const char *str);
 
 struct log_info;
 int bsc_vty_init(const struct log_info *cat);
diff --git a/openbsc/src/libbsc/bsc_vty.c b/openbsc/src/libbsc/bsc_vty.c
index 29f2501..6ca5048 100644
--- a/openbsc/src/libbsc/bsc_vty.c
+++ b/openbsc/src/libbsc/bsc_vty.c
@@ -112,6 +112,12 @@  struct cmd_node net_node = {
 	1,
 };
 
+struct cmd_node virt_net_node = {
+	VIRT_NET_NODE,
+	"%s(config-net-virt)# ",
+	1,
+};
+
 struct cmd_node bts_node = {
 	BTS_NODE,
 	"%s(config-net-bts)# ",
@@ -343,6 +349,41 @@  DEFUN(show_bts, show_bts_cmd, "show bts [<0-255>]",
 	return CMD_SUCCESS;
 }
 
+static void virt_net_dump_vty(struct vty *vty, struct gsm_virt_network *virt_net)
+{
+	vty_out(vty, "Virtual network %u%s", virt_net->nr, VTY_NEWLINE);
+	vty_out(vty, "  imsi-prefix %s%s", virt_net->imsi_prefix, VTY_NEWLINE);
+	vty_out(vty, "  Long network name: '%s'%s",
+		virt_net->name_long, VTY_NEWLINE);
+	vty_out(vty, "  Short network name: '%s'%s",
+		virt_net->name_short, VTY_NEWLINE);
+}
+
+DEFUN(show_virt_net, show_virt_net_cmd, "show virtual-network [<0-255>]",
+	SHOW_STR "Display information about a virtual network\n"
+		"Virtual network number")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+	int virt_net_nr;
+
+	if (argc != 0) {
+		/* use the virtual network number that the user has specified */
+		virt_net_nr = atoi(argv[0]);
+		if (virt_net_nr >= net->num_virt_net) {
+			vty_out(vty, "%% can't find virtual network '%s'%s", argv[0],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		virt_net_dump_vty(vty, gsm_virt_net_num(net, virt_net_nr));
+		return CMD_SUCCESS;
+	}
+	/* print all virtual networks */
+	for (virt_net_nr = 0; virt_net_nr < net->num_virt_net; virt_net_nr++)
+		virt_net_dump_vty(vty, gsm_virt_net_num(net, virt_net_nr));
+
+	return CMD_SUCCESS;
+}
+
 /* utility functions */
 static void parse_e1_link(struct gsm_e1_subslot *e1_link, const char *line,
 			  const char *ts, const char *ss)
@@ -743,6 +784,25 @@  static int config_write_bts(struct vty *v)
 	return CMD_SUCCESS;
 }
 
+static void config_write_virt_net_single(struct vty *vty, struct gsm_virt_network *virt_net)
+{
+	vty_out(vty, " virtual-network %u%s", virt_net->nr, VTY_NEWLINE);
+	vty_out(vty, "  imsi-prefix %s%s", virt_net->imsi_prefix, VTY_NEWLINE);
+	vty_out(vty, "  short name %s%s", virt_net->name_short, VTY_NEWLINE);
+	vty_out(vty, "  long name %s%s", virt_net->name_long, VTY_NEWLINE);
+}
+
+static int config_write_virt_net(struct vty *v)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(v);
+	struct gsm_virt_network *virt_net;
+
+	llist_for_each_entry(virt_net, &gsmnet->virt_net_list, list)
+		config_write_virt_net_single(v, virt_net);
+
+	return CMD_SUCCESS;
+}
+
 static int config_write_net(struct vty *vty)
 {
 	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
@@ -1612,6 +1672,76 @@  DEFUN(cfg_net_subscr_keep,
 	return CMD_SUCCESS;
 }
 
+DEFUN(cfg_virt_net,
+      cfg_virt_net_cmd,
+      "virtual-network <0-255>",
+      "Select a virtual network to configure\n"
+	"Virtual-network Number\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	int virt_net_nr = atoi(argv[0]);
+	struct gsm_virt_network *virt_net;
+
+	if (virt_net_nr > gsmnet->num_virt_net) {
+		vty_out(vty, "%% The next unused Virtual-network number is %u%s",
+			gsmnet->num_virt_net, VTY_NEWLINE);
+		return CMD_WARNING;
+	} else if (virt_net_nr == gsmnet->num_virt_net) {
+		/* allocate a new one */
+		virt_net = gsm_virt_net_alloc_register(gsmnet);
+	} else
+		virt_net = gsm_virt_net_num(gsmnet, virt_net_nr);
+
+	if (!virt_net) {
+		vty_out(vty, "%% Unable to allocate Virtual-network %u%s",
+			gsmnet->num_virt_net, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	vty->index = virt_net;
+	vty->index_sub = NULL;
+	vty->node = VIRT_NET_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_virt_net_imsi_prefix,
+      cfg_virt_net_imsi_prefix_cmd,
+      "imsi-prefix PREFIX",
+      "Set the IMSI prefix\n"
+      "prefix\n")
+{
+	struct gsm_virt_network *virt_net = vty->index;
+	if (!osmo_is_digits(argv[0])) {
+		vty_out(vty, "%% PREFIX has to be numeric%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	snprintf(virt_net->imsi_prefix, sizeof(virt_net->imsi_prefix), "%s", argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_virt_net_name_short,
+      cfg_virt_net_name_short_cmd,
+      "short name NAME",
+      "Set the short GSM network name\n" NAME_CMD_STR NAME_STR)
+{
+	struct gsm_virt_network *virt_net = vty->index;
+
+	bsc_replace_string(virt_net->network, &virt_net->name_short, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_virt_net_name_long,
+      cfg_virt_net_name_long_cmd,
+      "long name NAME",
+      "Set the long GSM network name\n" NAME_CMD_STR NAME_STR)
+{
+	struct gsm_virt_network *virt_net = vty->index;
+
+	bsc_replace_string(virt_net->network, &virt_net->name_long, argv[0]);
+	return CMD_SUCCESS;
+}
+
 /* per-BTS configuration */
 DEFUN(cfg_bts,
       cfg_bts_cmd,
@@ -3763,6 +3893,7 @@  int bsc_vty_init(const struct log_info *cat)
 
 
 	install_element_ve(&show_net_cmd);
+	install_element_ve(&show_virt_net_cmd);
 	install_element_ve(&show_bts_cmd);
 	install_element_ve(&show_trx_cmd);
 	install_element_ve(&show_ts_cmd);
@@ -3811,6 +3942,13 @@  int bsc_vty_init(const struct log_info *cat)
 	install_element(GSMNET_NODE, &cfg_net_subscr_keep_cmd);
 	install_element(GSMNET_NODE, &cfg_net_pag_any_tch_cmd);
 
+	install_element(GSMNET_NODE, &cfg_virt_net_cmd);
+	install_node(&virt_net_node, config_write_virt_net);
+	vty_install_default(VIRT_NET_NODE);
+	install_element(VIRT_NET_NODE, &cfg_virt_net_imsi_prefix_cmd);
+	install_element(VIRT_NET_NODE, &cfg_virt_net_name_short_cmd);
+	install_element(VIRT_NET_NODE, &cfg_virt_net_name_long_cmd);
+
 	install_element(GSMNET_NODE, &cfg_bts_cmd);
 	install_node(&bts_node, config_write_bts);
 	vty_install_default(BTS_NODE);
diff --git a/openbsc/src/libbsc/net_init.c b/openbsc/src/libbsc/net_init.c
index 568a0b8..1fe9f3e 100644
--- a/openbsc/src/libbsc/net_init.c
+++ b/openbsc/src/libbsc/net_init.c
@@ -71,6 +71,7 @@  struct gsm_network *gsm_network_init(uint16_t country_code, uint16_t network_cod
 	INIT_LLIST_HEAD(&net->trans_list);
 	INIT_LLIST_HEAD(&net->upqueue);
 	INIT_LLIST_HEAD(&net->bts_list);
+	INIT_LLIST_HEAD(&net->virt_net_list);
 
 	net->stats.chreq.total = osmo_counter_alloc("net.chreq.total");
 	net->stats.chreq.no_channel = osmo_counter_alloc("net.chreq.no_channel");
diff --git a/openbsc/src/libcommon/common_vty.c b/openbsc/src/libcommon/common_vty.c
index a0674f0..66a82d8 100644
--- a/openbsc/src/libcommon/common_vty.c
+++ b/openbsc/src/libcommon/common_vty.c
@@ -42,6 +42,15 @@  int bsc_vty_go_parent(struct vty *vty)
 		vty->node = CONFIG_NODE;
 		vty->index = NULL;
 		break;
+	case VIRT_NET_NODE:
+		vty->node = GSMNET_NODE;
+		{
+			/* set vty->index correctly ! */
+			struct gsm_virt_network *virt_net = vty->index;
+			vty->index = virt_net->network;
+			vty->index_sub = NULL;
+		}
+		break;
 	case BTS_NODE:
 		vty->node = GSMNET_NODE;
 		{
@@ -141,3 +150,13 @@  void bsc_replace_string(void *ctx, char **dst, const char *newstr)
 		talloc_free(*dst);
 	*dst = talloc_strdup(ctx, newstr);
 }
+
+int osmo_is_digits(const char *str)
+{
+	int i;
+	for (i = 0; i < strlen(str); i++) {
+		if (!isdigit(str[i]))
+			return 0;
+	}
+	return 1;
+}
diff --git a/openbsc/src/libcommon/gsm_data.c b/openbsc/src/libcommon/gsm_data.c
index 16035ed..6b33b18 100644
--- a/openbsc/src/libcommon/gsm_data.c
+++ b/openbsc/src/libcommon/gsm_data.c
@@ -192,6 +192,47 @@  const char *rrlp_mode_name(enum rrlp_mode mode)
 	return get_value_string(rrlp_mode_names, mode);
 }
 
+struct gsm_virt_network *gsm_virt_net_alloc(void *ctx)
+{
+	struct gsm_virt_network *virt_net = talloc_zero(ctx, struct gsm_virt_network);
+	if (!virt_net)
+		return NULL;
+	return virt_net;
+}
+
+struct gsm_virt_network *gsm_virt_net_alloc_register(struct gsm_network *net)
+{
+	struct gsm_virt_network *virt_net;
+
+	virt_net = gsm_virt_net_alloc(net);
+	if (!virt_net)
+		return NULL;
+
+	virt_net->nr = net->num_virt_net++;
+	virt_net->network = net;
+	strcpy(virt_net->imsi_prefix, "00101");
+	virt_net->name_short = talloc_strdup(net, "OpenBSC");
+	virt_net->name_long = talloc_strdup(net, "OpenBSC");
+
+	llist_add_tail(&virt_net->list, &net->virt_net_list);
+	return virt_net;
+}
+
+struct gsm_virt_network *gsm_virt_net_num(struct gsm_network *net, int num)
+{
+	struct gsm_virt_network *virt_net;
+
+	if (num >= net->num_virt_net)
+		return NULL;
+
+	llist_for_each_entry(virt_net, &net->virt_net_list, list) {
+		if (virt_net->nr == num)
+			return virt_net;
+	}
+
+	return NULL;
+}
+
 static const struct value_string bts_gprs_mode_names[] = {
 	{ BTS_GPRS_NONE,	"none" },
 	{ BTS_GPRS_GPRS,	"gprs" },
diff --git a/openbsc/src/libmsc/gsm_04_08.c b/openbsc/src/libmsc/gsm_04_08.c
index 1524ec4..cdfcb2d 100644
--- a/openbsc/src/libmsc/gsm_04_08.c
+++ b/openbsc/src/libmsc/gsm_04_08.c
@@ -702,6 +702,16 @@  static uint8_t bcdify(uint8_t value)
         return ret;
 }
 
+int is_prefix_match_imsi(char *imsi_prefix, char *imsi)
+{
+	size_t prefix_len = strlen(imsi_prefix);
+	size_t imsi_len = strlen(imsi);
+	if ((prefix_len <= imsi_len) &&
+	    (strncmp(imsi, imsi_prefix, prefix_len) == 0)) {
+		return 1;
+	}
+	return 0;
+}
 
 /* Section 9.2.15a */
 int gsm48_tx_mm_info(struct gsm_subscriber_connection *conn)
@@ -719,13 +729,29 @@  int gsm48_tx_mm_info(struct gsm_subscriber_connection *conn)
 	int tzunits;
 	int dst = 0;
 
+	char *name_long = net->name_long;
+	char *name_short = net->name_short;
+	int virt_net_nr;
+
 	msg->lchan = conn->lchan;
 
 	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
 	gh->proto_discr = GSM48_PDISC_MM;
 	gh->msg_type = GSM48_MT_MM_INFO;
 
-	if (net->name_long) {
+	if (net->num_virt_net) {
+		for (virt_net_nr = 0; virt_net_nr < net->num_virt_net; virt_net_nr++) {
+			struct gsm_virt_network* virt_net = gsm_virt_net_num(net, virt_net_nr);
+			if (virt_net &&
+			    is_prefix_match_imsi(virt_net->imsi_prefix, conn->subscr->imsi)) {
+				name_long = virt_net->name_long;
+				name_short = virt_net->name_short;
+				break;
+			}
+		}
+	}
+
+	if (name_long) {
 #if 0
 		name_len = strlen(net->name_long);
 		/* 10.5.3.5a */
@@ -741,8 +767,8 @@  int gsm48_tx_mm_info(struct gsm_subscriber_connection *conn)
 		/* FIXME: Use Cell Broadcast, not UCS-2, since
 		 * UCS-2 is only supported by later revisions of the spec */
 #endif
-		name_len = (strlen(net->name_long)*7)/8;
-		name_pad = (8 - strlen(net->name_long)*7)%8;
+		name_len = (strlen(name_long)*7)/8;
+		name_pad = (8 - strlen(name_long)*7)%8;
 		if (name_pad > 0)
 			name_len++;
 		/* 10.5.3.5a */
@@ -752,11 +778,11 @@  int gsm48_tx_mm_info(struct gsm_subscriber_connection *conn)
 		ptr8[2] = 0x80 | name_pad; /* Cell Broadcast DCS, no CI */
 
 		ptr8 = msgb_put(msg, name_len);
-		gsm_7bit_encode_n(ptr8, name_len, net->name_long, NULL);
+		gsm_7bit_encode_n(ptr8, name_len, name_long, NULL);
 
 	}
 
-	if (net->name_short) {
+	if (name_short) {
 #if 0
 		name_len = strlen(net->name_short);
 		/* 10.5.3.5a */
@@ -769,8 +795,8 @@  int gsm48_tx_mm_info(struct gsm_subscriber_connection *conn)
 		for (i = 0; i < name_len; i++)
 			ptr16[i] = htons(net->name_short[i]);
 #endif
-		name_len = (strlen(net->name_short)*7)/8;
-		name_pad = (8 - strlen(net->name_short)*7)%8;
+		name_len = (strlen(name_short)*7)/8;
+		name_pad = (8 - strlen(name_short)*7)%8;
 		if (name_pad > 0)
 			name_len++;
 		/* 10.5.3.5a */
@@ -780,7 +806,7 @@  int gsm48_tx_mm_info(struct gsm_subscriber_connection *conn)
 		ptr8[2] = 0x80 | name_pad; /* Cell Broadcast DCS, no CI */
 
 		ptr8 = msgb_put(msg, name_len);
-		gsm_7bit_encode_n(ptr8, name_len, net->name_short, NULL);
+		gsm_7bit_encode_n(ptr8, name_len, name_short, NULL);
 
 	}
 
diff --git a/openbsc/src/libmsc/smpp_vty.c b/openbsc/src/libmsc/smpp_vty.c
index 351e8be..72b816f 100644
--- a/openbsc/src/libmsc/smpp_vty.c
+++ b/openbsc/src/libmsc/smpp_vty.c
@@ -277,16 +277,6 @@  DEFUN(cfg_esme_no_passwd, cfg_esme_no_passwd_cmd,
 	return CMD_SUCCESS;
 }
 
-static int osmo_is_digits(const char *str)
-{
-	int i;
-	for (i = 0; i < strlen(str); i++) {
-		if (!isdigit(str[i]))
-			return 0;
-	}
-	return 1;
-}
-
 static const struct value_string route_errstr[] = {
 	{ -EEXIST,	"Route already exists" },
 	{ -ENODEV,	"Route does not exist" },
diff --git a/openbsc/tests/vty_test_runner.py b/openbsc/tests/vty_test_runner.py
index ecf5204..9a51260 100644
--- a/openbsc/tests/vty_test_runner.py
+++ b/openbsc/tests/vty_test_runner.py
@@ -133,6 +133,9 @@  class TestVTYGenericBSC(TestVTYBase):
         self.assertTrue(self.vty.verify("network",['']))
         self.assertEquals(self.vty.node(), 'config-net')
         self.checkForEndAndExit()
+        self.assertTrue(self.vty.verify("virtual-network 0",['']))
+        self.assertEquals(self.vty.node(), 'config-net-virt')
+        self.checkForEndAndExit()
         self.assertTrue(self.vty.verify("bts 0",['']))
         self.assertEquals(self.vty.node(), 'config-net-bts')
         self.checkForEndAndExit()
@@ -154,6 +157,12 @@  class TestVTYGenericBSC(TestVTYBase):
         self.assertEquals(self.vty.node(), 'config-net-bts')
         self.assertTrue(self.vty.verify("exit",['']))
         self.assertEquals(self.vty.node(), 'config-net')
+        self.assertTrue(self.vty.verify("virtual-network 1",['']))
+        self.assertEquals(self.vty.node(), 'config-net-virt')
+        self.checkForEndAndExit()
+        self.vty.command("write terminal")
+        self.assertTrue(self.vty.verify("exit",['']))
+        self.assertEquals(self.vty.node(), 'config-net')
         self.assertTrue(self.vty.verify("exit",['']))
         self.assertEquals(self.vty.node(), 'config')
         self.assertTrue(self.vty.verify("exit",['']))
@@ -342,6 +351,37 @@  class TestVTYNITB(TestVTYGenericBSC):
         res = self.vty.command('show subscriber imsi '+imsi)
         self.assert_(res != '% No subscriber found for imsi '+imsi)
 
+    def testVirtualNetworks(self):
+        self.vty.enable()
+        self.vty.command("configure terminal")
+        self.vty.command("network")
+        self.vty.command("virtual-network 0")
+
+        # Test invalid input
+        self.vty.verify("imsi-prefix 1234567abcd89", ['% PREFIX has to be numeric'])
+        self.vty.verify("short name Test Net", ['% Unknown command.'])
+        self.vty.verify("long name Test Network", ['% Unknown command.'])
+
+        # Set virtual-networks
+        self.vty.verify("imsi-prefix 00202", [''])
+        self.vty.verify("short name TestNet2", [''])
+        self.vty.verify("long name TestNetwork2", [''])
+        self.vty.verify("exit",[''])
+        self.vty.command("virtual-network 1")
+        self.vty.verify("imsi-prefix 00303300", [''])
+        self.vty.verify("short name TestNet3", [''])
+        self.vty.verify("long name TestNetwork3", [''])
+
+         # Verify settings
+        res = self.vty.command("write terminal")
+        self.assert_(res.find('virtual-network 0') > 0)
+        self.assert_(res.find('imsi-prefix 00202') > 0)
+        self.assert_(res.find('short name TestNet2') > 0)
+        self.assert_(res.find('long name TestNetwork2') > 0)
+        self.assert_(res.find('virtual-network 1') > 0)
+        self.assert_(res.find('imsi-prefix 00303300') > 0)
+        self.assert_(res.find('short name TestNet3') > 0)
+        self.assert_(res.find('long name TestNetwork3') > 0)
 
     def testSubscriberCreateDelete(self):
         self.vty.enable()