aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKeith Whyte <keith@rhizomatica.org>2023-07-14 02:48:28 +0100
committerKeith Whyte <keith@rhizomatica.org>2023-07-14 04:59:24 +0100
commitcea9044fe4deb5063a4afafa7592c2be51b194a2 (patch)
tree589f724918a16e2c5f492a884692e1cc5f24366d
parent306e7fa8533b302a09442b86b558ee5fbb95aa13 (diff)
Implement reserved IPv4 addresses for IMSIsrhizomatica/testing
Allows maintaining, via the VTY, a list of IMSI -> IPv4 address mappings to ensure the GGSN always assigns the same IP address to a UE identified by the IMSI. This is NOT an implenentation of static IP address support, it simply reserves IPs from the dynamic IP pool. No checks are done to enure that the address indeed forms part of the defined pool, this is left up to the operator to configure correctly. It may be desired to always have the same IP address assigned after a PDP context is deleted and recreated by the device, by toggling mobile data, or airplane mode. This could be useful, for example, in order to avoid having to adjust firewall or packet capture filters. One might also have a small subset of IMSIs on a network that should have a special set of firewall rules, for access to internal infrastructure or traffic shaping exemptions, as some examples. This is intented as a developer or small operator feature, but it is not the intention here that one would go about assigning 'static' IP addresses to an entire HLR of subscribers via the GGSN vty.
-rw-r--r--ggsn/ggsn.c94
-rw-r--r--ggsn/ggsn.h16
-rw-r--r--ggsn/ggsn_vty.c58
3 files changed, 167 insertions, 1 deletions
diff --git a/ggsn/ggsn.c b/ggsn/ggsn.c
index a0479a1..91dfadd 100644
--- a/ggsn/ggsn.c
+++ b/ggsn/ggsn.c
@@ -328,6 +328,74 @@ int apn_start(struct apn_ctx *apn)
return 0;
}
+static struct imsi_map_entry *apn_imsi_map_lookup_by_imsi(const char *imsi, const struct apn_ctx_ip *ctx)
+{
+ struct imsi_map_entry *map;
+ llist_for_each_entry(map, &ctx->imsi_ip_map, list) {
+ if (!strcmp(imsi, map->imsi))
+ return map;
+ }
+ return NULL;
+}
+
+static struct imsi_map_entry *apn_imsi_map_lookup_by_ip(const struct in46_addr *addr, const struct apn_ctx_ip *ctx)
+{
+ struct imsi_map_entry *map;
+ if (!addr)
+ return NULL;
+ llist_for_each_entry(map, &ctx->imsi_ip_map, list) {
+ if (in46a_equal(addr, &map->addr))
+ return map;
+ }
+ return NULL;
+}
+
+int apn_imsi_ip_map_add(const char *imsi, const char *ip, struct apn_ctx_ip *ctx)
+{
+ struct imsi_map_entry *map;
+ struct in46_addr addr = { 0 };
+ size_t t;
+
+ if (ippool_aton(&addr, &t, ip, 0))
+ return -EINVAL;
+ if (apn_imsi_map_lookup_by_imsi(imsi, ctx) || apn_imsi_map_lookup_by_ip(&addr, ctx))
+ return -EEXIST;
+
+ map = talloc_zero(NULL, struct imsi_map_entry);
+ if (!map)
+ return -ENOMEM;
+ if (ippool_aton(&map->addr, &t, ip, 0)) {
+ talloc_free(map);
+ return -EINVAL;
+ }
+
+ osmo_strlcpy(map->imsi, imsi, sizeof(map->imsi));
+ llist_add(&map->list, &ctx->imsi_ip_map);
+
+ return 0;
+}
+
+int apn_imsi_ip_map_del(const char *imsi, const char *ip, struct apn_ctx_ip *ctx)
+{
+ struct imsi_map_entry *map;
+
+ map = apn_imsi_map_lookup_by_imsi(imsi, ctx);
+ if (!map)
+ return -ENODEV;
+
+ llist_del(&map->list);
+ talloc_free(map);
+
+ return 0;
+}
+
+static struct imsi_map_entry *imsi_has_reserved_ip(const char *imsi, struct apn_ctx_ip *ctx)
+{
+ if (llist_empty(&ctx->imsi_ip_map))
+ return NULL;
+ return apn_imsi_map_lookup_by_imsi(imsi, ctx);
+}
+
static bool send_trap(const struct gsn_t *gsn, const struct pdp_t *pdp, const struct ippoolm_t *member, const char *var)
{
char addrbuf[256];
@@ -385,7 +453,7 @@ static int delete_context(struct pdp_t *pdp)
return 0;
}
-static bool apn_supports_ipv4(const struct apn_ctx *apn)
+bool apn_supports_ipv4(const struct apn_ctx *apn)
{
if (apn->v4.cfg.static_prefix.addr.len || apn->v4.cfg.dynamic_prefix.addr.len)
return true;
@@ -442,6 +510,7 @@ int create_context_ind(struct pdp_t *pdp)
char *apn_name;
struct sgsn_peer *sgsn;
struct pdp_priv_t *pdp_priv;
+ struct imsi_map_entry *imsi_map;
apn_name = osmo_apn_to_str(name_buf, pdp->apn_req.v, pdp->apn_req.l);
LOGPPDP(LOGL_DEBUG, pdp, "Processing create PDP context request for APN '%s'\n",
@@ -495,6 +564,17 @@ int create_context_ind(struct pdp_t *pdp)
LOGPPDP(LOGL_ERROR, pdp, "Failed to store APN '%s'\n", apn->cfg.name);
pdp->apn_use.l = rc;
+ /* Check if we have a entry in the reserved IP map for this IMSI */
+ imsi_map = imsi_has_reserved_ip(imsi_gtp2str(&pdp->imsi), &apn->v4);
+ if (imsi_map) {
+ /* Override (prefill) any requested (dynamic|static) IP
+ * in the EUA with the one from the configuration map. */
+ addr[0].len = 4;
+ memcpy(&addr[0].v4.s_addr, &imsi_map->addr.v4, 4);
+ LOGPPDP(LOGL_INFO, pdp, "IMSI[%s] has an entry[%s] in reserved IP map\n",
+ imsi_gtp2str(&pdp->imsi), in46a_ntoa(&imsi_map->addr));
+ }
+
/* Allocate dynamic addresses from the pool */
for (i = 0; i < num_addr; i++) {
if (in46a_is_v4(&addr[i])) {
@@ -503,6 +583,18 @@ int create_context_ind(struct pdp_t *pdp)
goto err_wrong_af;
rc = ippool_newip(apn->v4.pool, &member, &addr[i], 0);
+
+ if (!imsi_map) {
+ /* This IMSI does not have a reserved IP, check that we did not assign one */
+ while (apn_imsi_map_lookup_by_ip(&member->addr, &apn->v4)) {
+ LOGPPDP(LOGL_INFO, pdp, "Returned IP[%s] is reserved, trying again.\n",
+ in46a_ntoa(&member->addr));
+ ippool_freeip(member->pool, member);
+ rc = ippool_newip(apn->v4.pool, &member, &addr[i], 0);
+ }
+
+ }
+ LOGPPDP(LOGL_INFO, pdp, "Got IP[%s] from the pool.\n", in46a_ntoa(&member->addr));
if (rc < 0)
goto err_pool_full;
/* copy back */
diff --git a/ggsn/ggsn.h b/ggsn/ggsn.h
index 1abbc9a..44fd3cc 100644
--- a/ggsn/ggsn.h
+++ b/ggsn/ggsn.h
@@ -8,6 +8,7 @@
#include <osmocom/core/timer.h>
#include <osmocom/core/tdef.h>
#include <osmocom/ctrl/control_if.h>
+#include <osmocom/gsm/protocol/gsm_23_003.h>
#include "../lib/tun.h"
#include "../lib/ippool.h"
@@ -35,6 +36,9 @@ struct apn_ctx_ip {
/* v4 address pool */
struct ippool_t *pool;
+ /* Static IMSI to IPv4 reserved address mappings. */
+ struct llist_head imsi_ip_map;
+
};
struct apn_name {
@@ -47,6 +51,15 @@ enum apn_gtpu_mode {
APN_GTPU_MODE_KERNEL_GTP,
};
+/*
+ * IMSI to static IP map handling
+ */
+struct imsi_map_entry {
+ struct llist_head list;
+ char imsi[OSMO_IMSI_BUF_SIZE];
+ struct in46_addr addr;
+};
+
struct apn_ctx {
/* list of APNs inside GGSN */
struct llist_head list;
@@ -161,6 +174,9 @@ extern int ggsn_stop(struct ggsn_ctx *ggsn);
extern int apn_start(struct apn_ctx *apn);
extern int apn_stop(struct apn_ctx *apn);
void ggsn_close_one_pdp(struct pdp_t *pdp);
+bool apn_supports_ipv4(const struct apn_ctx *apn);
+int apn_imsi_ip_map_add(const char *imsi, const char *ip, struct apn_ctx_ip *ctx);
+int apn_imsi_ip_map_del(const char *imsi, const char *ip, struct apn_ctx_ip *ctx);
#define LOGPAPN(level, apn, fmt, args...) \
LOGP(DGGSN, level, "APN(%s): " fmt, (apn)->cfg.name, ## args)
diff --git a/ggsn/ggsn_vty.c b/ggsn/ggsn_vty.c
index 2d49a94..e8b9124 100644
--- a/ggsn/ggsn_vty.c
+++ b/ggsn/ggsn_vty.c
@@ -112,6 +112,7 @@ struct apn_ctx *ggsn_find_or_create_apn(struct ggsn_ctx *ggsn, const char *name)
apn->cfg.shutdown = true;
apn->cfg.tx_gpdu_seq = true;
INIT_LLIST_HEAD(&apn->cfg.name_list);
+ INIT_LLIST_HEAD(&apn->v4.imsi_ip_map);
llist_add_tail(&apn->list, &ggsn->apn_list);
return apn;
@@ -562,6 +563,58 @@ DEFUN(cfg_apn_no_ip_ifconfig, cfg_apn_no_ip_ifconfig_cmd,
return CMD_SUCCESS;
}
+DEFUN(cfg_apn_imsi_ip_map, cfg_apn_imsi_ip_map_cmd,
+ "imsi-ip-map (add|del) IMSI [A.B.C.D]",
+ "List of IMSI to RESERVED ip4 mappings\n"
+ "Add IMSI/IP pair to mappings\n"
+ "Remove IMSI/IP pair from mappings\n"
+ "IMSI of the subscriber\n"
+ "IP for the subscriber\n")
+{
+ char imsi_sanitized[GSM23003_IMSI_MAX_DIGITS + 1];
+ const char *op = argv[0];
+ const char *imsi = imsi_sanitized;
+ size_t len = strnlen(argv[1], GSM23003_IMSI_MAX_DIGITS + 1);
+ const char *ip = argv[2];
+ int rc;
+
+ struct apn_ctx *apn = (struct apn_ctx *) vty->index;
+
+ if (!apn_supports_ipv4(apn)) {
+ vty_out(vty, "%% APN does not support ipv4 addresses%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ memset(imsi_sanitized, '0', GSM23003_IMSI_MAX_DIGITS);
+ imsi_sanitized[GSM23003_IMSI_MAX_DIGITS] = '\0';
+
+ /* Sanitize IMSI */
+ if (len > GSM23003_IMSI_MAX_DIGITS) {
+ vty_out(vty, "%% IMSI (%s) too long (max %u digits) -- ignored!%s",
+ argv[1], GSM23003_IMSI_MAX_DIGITS, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ osmo_strlcpy(imsi_sanitized + GSM23003_IMSI_MAX_DIGITS - len, argv[1],
+ sizeof(imsi_sanitized) - (GSM23003_IMSI_MAX_DIGITS - len));
+
+ if (!strcmp(op, "add")) {
+ if (!ip) {
+ vty_out(vty, "%% unable to %s IMSI without IP address%s", op, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ rc = apn_imsi_ip_map_add(imsi, ip, &apn->v4);
+ } else {
+ rc = apn_imsi_ip_map_del(imsi, ip, &apn->v4);
+ }
+ if (rc < 0) {
+ vty_out(vty, "%% unable to %s IMSI%s", op, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
DEFUN(cfg_apn_ipv6_prefix, cfg_apn_ipv6_prefix_cmd,
"ipv6 prefix (static|dynamic) X:X::X:X/M",
IP6_STR PREFIX_STR "IPv6 Address/Prefix-Length\n")
@@ -726,6 +779,7 @@ static void vty_dump_prefix(struct vty *vty, const char *pre, const struct in46_
static void config_write_apn(struct vty *vty, struct apn_ctx *apn)
{
unsigned int i;
+ struct imsi_map_entry *map;
vty_out(vty, " apn %s%s", apn->cfg.name, VTY_NEWLINE);
if (apn->cfg.description)
@@ -762,6 +816,9 @@ static void config_write_apn(struct vty *vty, struct apn_ctx *apn)
if (apn->v4.cfg.ifconfig_prefix.addr.len)
vty_dump_prefix(vty, " ip ifconfig", &apn->v4.cfg.ifconfig_prefix);
+ llist_for_each_entry(map, &apn->v4.imsi_ip_map, list)
+ vty_out(vty, " imsi-ip-map add %s %s%s", map->imsi, in46a_ntoa(&map->addr), VTY_NEWLINE);
+
/* IPv6 prefixes + DNS */
if (apn->v6.cfg.static_prefix.addr.len)
vty_dump_prefix(vty, " ipv6 prefix static", &apn->v6.cfg.static_prefix);
@@ -1131,6 +1188,7 @@ int ggsn_vty_init(void)
install_element(APN_NODE, &cfg_apn_ipdown_script_cmd);
install_element(APN_NODE, &cfg_apn_no_ipdown_script_cmd);
install_element(APN_NODE, &cfg_apn_ip_prefix_cmd);
+ install_element(APN_NODE, &cfg_apn_imsi_ip_map_cmd);
install_element(APN_NODE, &cfg_apn_ipv6_prefix_cmd);
install_element(APN_NODE, &cfg_apn_ip_dns_cmd);
install_element(APN_NODE, &cfg_apn_ipv6_dns_cmd);