diff options
-rw-r--r-- | ggsn/ggsn.c | 94 | ||||
-rw-r--r-- | ggsn/ggsn.h | 16 | ||||
-rw-r--r-- | ggsn/ggsn_vty.c | 58 |
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 7414b6a..df919e7 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); @@ -1128,6 +1185,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); |