207 lines
6.2 KiB
C
207 lines
6.2 KiB
C
/* GTP Hub Implementation */
|
|
|
|
/* (C) 2015 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
|
* All Rights Reserved
|
|
*
|
|
* gtphub_ext.c -- ext means extern. 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;
|
|
}
|