osmo-bsc/src/gprs/gtphub_ares.c

221 lines
6.2 KiB
C
Raw Normal View History

/* 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[GSM23003_IMSI_MAX_DIGITS+1];
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);
osmo_strlcpy(lookup->apn_oi_str, apn_oi_str,
sizeof(lookup->apn_oi_str));
}
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_DEBUG, "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;
osmo_strlcpy(lookup->imsi_str, imsi_str, sizeof(lookup->imsi_str));
osmo_strlcpy(lookup->apn_ni_str, apn_ni_str,
sizeof(lookup->apn_ni_str));
make_addr_str(lookup);
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) {
LOGP(DGTPHUB, LOGL_DEBUG,
"Query already pending for %s\n",
lookup->apn_oi_str);
/* A query already pending. Just tip our hat. */
return NULL;
}
}
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) {
LOGP(DGTPHUB, LOGL_DEBUG,
"GGSN resolved from cache: %s -> %s\n",
lookup->apn_oi_str,
gtphub_port_str(resolved->peer));
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_DEBUG,
"Sending out DNS query for %s..."
" (Returning failure, hoping for a retry once resolution"
" has concluded)\n",
lookup->apn_oi_str);
llist_add(&lookup->entry, &hub->ggsn_lookups);
lookup->expiry_entry.del_cb = ggsn_lookup_del_cb;
gtphub: track tunnels explicitly. So far, gtphub worked perfectly by only tracking single TEIs ... for probably most uses. But a Ctrl plane tunnel may have expired despite a still active corresponding User plane tunnel. The User plane would continue to work indefinitely, but if any Ctrl messages followed after more than six hours of Ctrl silence, they would have been dropped due to an expired TEI mapping. We want to - combine expiry of a user TEI with its ctrl TEI. (done in this patch) - upon delete PDP context, remove both user and ctrl TEI mappings. (future) - when a peer indicates a restart counter bump, invalidate its tunnels. (future) To facilitate these, track tunnels, complete with both SGSN's and GGSN's address, original and replaced TEIs, all for both user and ctrl plane, in a single struct. A single expiry entry handles the entire tunnel, instead of previously four separate expiries for each endpoint identifier. Add the concept of a "side", being either GGSN or SGSN, to index tunnel endpoint structs, and so on. Track the originating side in the gtp_packet_desc. Add header_tei_rx: set_tei() overwrites header_tei, but the originally received header TEI is still needed to match a Create PDP Context Response up with its Request (and for logging). Adjust the test suite to expect tunnel listing strings instead of TEI mappings, with a bonus of making it a lot easier to grok, and including the IP addresses. Add regression test for refreshing tunnel expiry upon use. Note: the current implementation is as slow as can possibly be, iterating all the tunnels all the time. Optimizations are kept for a future commit, on purpose. BTW, the sequence number mapping/unmapping structures remain unchanged. Sponsored-by: On-Waves ehi
2015-11-24 12:31:06 +00:00
expiry_add(&hub->expire_quickly, &lookup->expiry_entry, gtphub_now());
start_ares_query(lookup);
return NULL;
}