diff options
Diffstat (limited to 'openbsc/src/gprs/gtphub_ares.c')
-rw-r--r-- | openbsc/src/gprs/gtphub_ares.c | 218 |
1 files changed, 218 insertions, 0 deletions
diff --git a/openbsc/src/gprs/gtphub_ares.c b/openbsc/src/gprs/gtphub_ares.c new file mode 100644 index 000000000..4cacf88fd --- /dev/null +++ b/openbsc/src/gprs/gtphub_ares.c @@ -0,0 +1,218 @@ +/* GTP Hub Implementation */ + +/* (C) 2015 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * gtphub_ares.c. + * + * This file is kept separate so that these functions can be wrapped for + * gtphub_test.c. When a function and its callers are in the same compilational + * unit, the wrappability may be optimized away. + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <unistd.h> + +#include <openbsc/gtphub.h> +#include <openbsc/debug.h> + +#include <osmocom/core/utils.h> +#include <osmocom/gsm/apn.h> + +/* TODO split GRX ares from sgsn into a separate struct and allow use without + * globals. */ +#include <openbsc/sgsn.h> +extern struct sgsn_instance *sgsn; + +struct sgsn_instance sgsn_inst = { 0 }; +struct sgsn_instance *sgsn = &sgsn_inst; + +extern void *osmo_gtphub_ctx; + +int gtphub_ares_init(struct gtphub *hub) +{ + return sgsn_ares_init(sgsn); +} + +struct ggsn_lookup { + struct llist_head entry; + struct expiring_item expiry_entry; + + struct gtphub *hub; + + char imsi_str[GSM_IMSI_LENGTH]; + char apn_ni_str[GSM_APN_LENGTH]; + char apn_oi_str[GSM_APN_LENGTH]; + int have_3dig_mnc; +}; + +static int start_ares_query(struct ggsn_lookup *lookup); + +static void ggsn_lookup_cb(void *arg, int status, int timeouts, + struct hostent *hostent) +{ + struct ggsn_lookup *lookup = arg; + LOGP(DGTPHUB, LOGL_NOTICE, "ggsn_lookup_cb(%p / %p)", lookup, + &lookup->expiry_entry); + + if (status != ARES_SUCCESS) { + LOGP(DGTPHUB, LOGL_ERROR, "DNS query failed.\n"); + + /* Need to try with three digits now */ + if (!lookup->have_3dig_mnc) { + lookup->have_3dig_mnc = 1; + if (start_ares_query(lookup) == 0) + return; + } + + LOGP(DGTPHUB, LOGL_ERROR, "Failed to resolve GGSN. (%p)\n", + lookup); + goto remove_from_queue; + } + + struct gsn_addr resolved_addr; + if (hostent->h_length > sizeof(resolved_addr.buf)) { + LOGP(DGTPHUB, LOGL_ERROR, "Addr size too large: %d > %d\n", + (int)hostent->h_length, (int)sizeof(resolved_addr.buf)); + goto remove_from_queue; + } + + /* Get the first addr from the list */ + char *addr0 = hostent->h_addr_list[0]; + if (!addr0) { + LOGP(DGTPHUB, LOGL_ERROR, "No host address.\n"); + goto remove_from_queue; + } + + memcpy(resolved_addr.buf, addr0, hostent->h_length); + resolved_addr.len = hostent->h_length; + + LOGP(DGTPHUB, LOGL_NOTICE, "resolved addr %s\n", + osmo_hexdump((unsigned char*)&resolved_addr, + sizeof(resolved_addr))); + + gtphub_resolved_ggsn(lookup->hub, lookup->apn_oi_str, &resolved_addr, + gtphub_now()); + +remove_from_queue: + LOGP(DGTPHUB, LOGL_ERROR, "Removing GGSN lookup. (%p / %p)\n", lookup, + &lookup->expiry_entry); + expiring_item_del(&lookup->expiry_entry); +} + +static void make_addr_str(struct ggsn_lookup *lookup) +{ + char *apn_oi_str; + apn_oi_str = osmo_apn_qualify_from_imsi(lookup->imsi_str, + lookup->apn_ni_str, + lookup->have_3dig_mnc); + strncpy(lookup->apn_oi_str, apn_oi_str, sizeof(lookup->apn_oi_str)); + lookup->apn_oi_str[sizeof(lookup->apn_oi_str)-1] = '\0'; +} + +static int start_ares_query(struct ggsn_lookup *lookup) +{ + LOGP(DGTPHUB, LOGL_DEBUG, "Going to query %s (%p / %p)\n", + lookup->apn_oi_str, lookup, &lookup->expiry_entry); + + int rc = sgsn_ares_query(sgsn, lookup->apn_oi_str, ggsn_lookup_cb, + lookup); + if (rc != 0) + LOGP(DGTPHUB, LOGL_ERROR, "Failed to start ares query.\n"); + return rc; +} + +static void ggsn_lookup_del_cb(struct expiring_item *expi) +{ + struct ggsn_lookup *lookup; + lookup = container_of(expi, struct ggsn_lookup, expiry_entry); + + LOGP(DGTPHUB, LOGL_NOTICE, "ggsn_lookup_del_cb(%p / %p)\n", lookup, + expi); + + lookup->expiry_entry.del_cb = 0; + expiring_item_del(expi); + + llist_del(&lookup->entry); + talloc_free(lookup); +} + +struct gtphub_peer_port *gtphub_resolve_ggsn_addr(struct gtphub *hub, + const char *imsi_str, + const char *apn_ni_str) +{ + OSMO_ASSERT(imsi_str); + OSMO_ASSERT(apn_ni_str); + + struct ggsn_lookup *lookup = talloc_zero(osmo_gtphub_ctx, + struct ggsn_lookup); + OSMO_ASSERT(lookup); + + LOGP(DGTPHUB, LOGL_NOTICE, "Request to resolve IMSI" + " '%s' with APN-NI '%s' (%p / %p)\n", + imsi_str, apn_ni_str, lookup, &lookup->expiry_entry); + + expiring_item_init(&lookup->expiry_entry); + lookup->hub = hub; + + strncpy(lookup->imsi_str, imsi_str, sizeof(lookup->imsi_str)); + lookup->imsi_str[sizeof(lookup->imsi_str)-1] = '\0'; + + strncpy(lookup->apn_ni_str, apn_ni_str, sizeof(lookup->apn_ni_str)); + lookup->apn_ni_str[sizeof(lookup->apn_ni_str)-1] = '\0'; + + make_addr_str(lookup); + + LOGP(DGTPHUB, LOGL_NOTICE, "looking for active queries...\n"); + struct ggsn_lookup *active; + llist_for_each_entry(active, &hub->ggsn_lookups, entry) { + if (strncmp(active->apn_oi_str, lookup->apn_oi_str, + sizeof(lookup->apn_oi_str)) == 0) { + /* A query already pending. Just tip our hat. */ + return NULL; + } + } + + LOGP(DGTPHUB, LOGL_NOTICE, "looking for already resolved GGSNs...\n"); + struct gtphub_resolved_ggsn *resolved; + llist_for_each_entry(resolved, &hub->resolved_ggsns, entry) { + if (strncmp(resolved->apn_oi_str, lookup->apn_oi_str, + sizeof(lookup->apn_oi_str)) == 0) { + /* Already resolved. */ + return resolved->peer; + } + } + + /* Kick off a resolution, but so far return nothing. The hope is that + * the peer will resend the request (a couple of times), and by then + * the GGSN will be resolved. */ + LOGP(DGTPHUB, LOGL_NOTICE, "kick off resolution.\n"); + + llist_add(&lookup->entry, &hub->ggsn_lookups); + + lookup->expiry_entry.del_cb = ggsn_lookup_del_cb; + expiry_add(&hub->expire_seq_maps, &lookup->expiry_entry, gtphub_now()); + + start_ares_query(lookup); + LOGP(DGTPHUB, LOGL_NOTICE, "Resolving %s %s ..." + " (Returning failure, hoping for a retry" + "once resolution has concluded)\n", + imsi_str, apn_ni_str); + + return NULL; +} |