Add default APN for each EUA Type

MS may request an unknown APN. In this case we will use
the APN configured in the vty as default-apn but if
the type support does not match the request we will fail.

The commit adds two more vty commands to configure a default
vpn for v6 and for v4v6

Change-Id: I03fcf8a1532bd9988ea99a6afd3dc325174ce9d6
Fixes: OS#4511
diff --git a/ggsn/ggsn.c b/ggsn/ggsn.c
index 159362f..81d8509 100644
--- a/ggsn/ggsn.c
+++ b/ggsn/ggsn.c
@@ -446,16 +446,44 @@
 	LOGPPDP(LOGL_DEBUG, pdp, "Processing create PDP context request for APN '%s'\n",
 		apn_name ? name_buf : "(NONE)");
 
+	/* FIXME: we manually force all context requests to dynamic here! */
+	if (pdp->eua.l > 2)
+		pdp->eua.l = 2;
+
+	memset(addr, 0, sizeof(addr));
+	if ((num_addr = in46a_from_eua(&pdp->eua, addr)) < 0) {
+		LOGPPDP(LOGL_ERROR, pdp, "Cannot decode EUA from MS/SGSN: %s\n",
+			osmo_hexdump(pdp->eua.v, pdp->eua.l));
+		gtp_create_context_resp(gsn, pdp, GTPCAUSE_UNKNOWN_PDP);
+		return 0;
+	}
+
 	/* First find an exact APN name match */
 	if (apn_name != NULL)
 		apn = ggsn_find_apn(ggsn, name_buf);
 	/* ignore if the APN has not been started */
 	if (apn && !apn->started)
 		apn = NULL;
-
 	/* then try default (if any) */
-	if (!apn)
-		apn = ggsn->cfg.default_apn;
+	if (!apn) {
+		switch (num_addr) {
+		case 2:
+			apn = ggsn->cfg.default_apn_v4v6;
+		break;
+		case 1:
+			if (in46a_is_v4(&addr[0])) {
+				apn = ggsn->cfg.default_apn_v4;
+			} else {
+				apn = ggsn->cfg.default_apn_v6;
+			}
+		break;
+		default:
+			/* in46a_from_eua() returned other than 1 or 2 ? */
+			OSMO_ASSERT(0);
+		break;
+		}
+	}
+
 	/* ignore if the APN has not been started */
 	if (apn && !apn->started)
 		apn = NULL;
@@ -467,23 +495,11 @@
 		return 0;
 	}
 
-	/* FIXME: we manually force all context requests to dynamic here! */
-	if (pdp->eua.l > 2)
-		pdp->eua.l = 2;
-
 	memcpy(pdp->qos_neg0, pdp->qos_req0, sizeof(pdp->qos_req0));
 
 	memcpy(pdp->qos_neg.v, pdp->qos_req.v, pdp->qos_req.l);	/* TODO */
 	pdp->qos_neg.l = pdp->qos_req.l;
 
-	memset(addr, 0, sizeof(addr));
-	if ((num_addr = in46a_from_eua(&pdp->eua, addr)) < 0) {
-		LOGPPDP(LOGL_ERROR, pdp, "Cannot decode EUA from MS/SGSN: %s\n",
-			osmo_hexdump(pdp->eua.v, pdp->eua.l));
-		gtp_create_context_resp(gsn, pdp, GTPCAUSE_UNKNOWN_PDP);
-		return 0;
-	}
-
 	/* Store the actual APN for logging and the VTY */
 	rc = osmo_apn_from_str(pdp->apn_use.v, sizeof(pdp->apn_use.v), apn->cfg.name);
 	if (rc < 0) /* Unlikely this would happen, but anyway... */
diff --git a/ggsn/ggsn.h b/ggsn/ggsn.h
index 82984a0..51f4ec1 100644
--- a/ggsn/ggsn.h
+++ b/ggsn/ggsn.h
@@ -115,8 +115,10 @@
 		char *name;
 		/* Description string */
 		char *description;
-		/* an APN that shall be used as default for any non-matching APN */
-		struct apn_ctx *default_apn;
+		/* APNs that shall be used as default for any non-matching APN */
+		struct apn_ctx *default_apn_v4;
+		struct apn_ctx *default_apn_v6;
+		struct apn_ctx *default_apn_v4v6;
 		/* ADdress to which we listen for GTP */
 		struct in46_addr listen_addr;
 		/* Local GTP-C address advertised in GTP */
diff --git a/ggsn/ggsn_vty.c b/ggsn/ggsn_vty.c
index cb92a8a..740ac08 100644
--- a/ggsn/ggsn_vty.c
+++ b/ggsn/ggsn_vty.c
@@ -269,30 +269,55 @@
 	return CMD_SUCCESS;
 }
 
-DEFUN(cfg_ggsn_default_apn, cfg_ggsn_default_apn_cmd,
-	"default-apn NAME",
+DEFUN(cfg_ggsn_default_apn_v4, cfg_ggsn_default_apn_v4_cmd,
+	"default-apn (v4|v6|v4v6) NAME",
 	"Set a default-APN to be used if no other APN matches\n"
+	"Set a default-APN to be used if no other APN v4 matches\n"
+	"Set a default-APN to be used if no other APN v6 matches\n"
+	"Set a default-APN to be used if no other APN v4v6 matches\n"
 	"APN Name\n")
 {
 	struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index;
 	struct apn_ctx *apn;
 
-	apn = ggsn_find_apn(ggsn, argv[0]);
+	/* backwards compatibility with 'default-apn NAME'.  */
+	const char *apn_name = (argc > 1) ? argv[1] : argv[0];
+	const char *apn_type = (argc > 1) ? argv[0] : "v4";
+
+	apn = ggsn_find_apn(ggsn, apn_name);
 	if (!apn) {
-		vty_out(vty, "%% No APN of name '%s' found%s", argv[0], VTY_NEWLINE);
+		vty_out(vty, "%% No APN of name '%s' found%s", apn_name, VTY_NEWLINE);
 		return CMD_WARNING;
 	}
 
-	ggsn->cfg.default_apn = apn;
+	if (!strcmp(apn_type, "v4"))
+		ggsn->cfg.default_apn_v4 = apn;
+	if (!strcmp(apn_type, "v6"))
+		ggsn->cfg.default_apn_v6 = apn;
+	if (!strcmp(apn_type, "v4v6"))
+		ggsn->cfg.default_apn_v4v6 = apn;
 	return CMD_SUCCESS;
 }
 
-DEFUN(cfg_ggsn_no_default_apn, cfg_ggsn_no_default_apn_cmd,
-	"no default-apn",
-	NO_STR "Remove default-APN to be used if no other APN matches\n")
+ALIAS_DEPRECATED(cfg_ggsn_default_apn_v4, cfg_ggsn_default_apn_cmd,
+	"default-apn NAME",
+	"Set a default-APN to be used if no other APN matches\n"
+	"APN Name\n")
+
+DEFUN(cfg_ggsn_no_default_apn_v4, cfg_ggsn_no_default_apn_v4_cmd,
+	"no default-apn (v4|v6|v4v6)",
+	NO_STR "Remove default-APN to be used if no other APN v4 matches\n"
+	"Remove default-APN to be used if no other APN v4 matches\n"
+	"Remove default-APN to be used if no other APN v6 matches\n"
+	"Remove default-APN to be used if no other APN v4v6 matches\n")
 {
 	struct ggsn_ctx *ggsn = (struct ggsn_ctx *) vty->index;
-	ggsn->cfg.default_apn = NULL;
+	if (!strcmp(argv[0], "v4"))
+		ggsn->cfg.default_apn_v4 = NULL;
+	if (!strcmp(argv[0], "v6"))
+		ggsn->cfg.default_apn_v6 = NULL;
+	if (!strcmp(argv[0], "v4v6"))
+		ggsn->cfg.default_apn_v4v6 = NULL;
 	return CMD_SUCCESS;
 }
 
@@ -790,8 +815,12 @@
 			vty_out(vty, " gtp user-ip %s%s", in46a_ntoa(&ggsn->cfg.gtpu_addr), VTY_NEWLINE);
 		llist_for_each_entry(apn, &ggsn->apn_list, list)
 			config_write_apn(vty, apn);
-		if (ggsn->cfg.default_apn)
-			vty_out(vty, " default-apn %s%s", ggsn->cfg.default_apn->cfg.name, VTY_NEWLINE);
+		if (ggsn->cfg.default_apn_v4)
+			vty_out(vty, " default-apn v4 %s%s", ggsn->cfg.default_apn_v4->cfg.name, VTY_NEWLINE);
+		if (ggsn->cfg.default_apn_v6)
+			vty_out(vty, " default-apn v6 %s%s", ggsn->cfg.default_apn_v6->cfg.name, VTY_NEWLINE);
+		if (ggsn->cfg.default_apn_v4v6)
+			vty_out(vty, " default-apn v4v6 %s%s", ggsn->cfg.default_apn_v4v6->cfg.name, VTY_NEWLINE);
 		if (ggsn->cfg.echo_interval)
 			vty_out(vty, " echo-interval %u%s", ggsn->cfg.echo_interval, VTY_NEWLINE);
 		/* must be last */
@@ -1096,7 +1125,8 @@
 	install_element(GGSN_NODE, &cfg_ggsn_apn_cmd);
 	install_element(GGSN_NODE, &cfg_ggsn_no_apn_cmd);
 	install_element(GGSN_NODE, &cfg_ggsn_default_apn_cmd);
-	install_element(GGSN_NODE, &cfg_ggsn_no_default_apn_cmd);
+	install_element(GGSN_NODE, &cfg_ggsn_default_apn_v4_cmd);
+	install_element(GGSN_NODE, &cfg_ggsn_no_default_apn_v4_cmd);
 	install_element(GGSN_NODE, &cfg_ggsn_show_sgsn_cmd);
 	install_element(GGSN_NODE, &cfg_ggsn_echo_interval_cmd);
 	install_element(GGSN_NODE, &cfg_ggsn_no_echo_interval_cmd);