/* GTP Hub Implementation */ /* (C) 2015 by sysmocom s.f.m.c. GmbH * 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 . */ #include #include #include #include #include #include /* TODO split GRX ares from sgsn into a separate struct and allow use without * globals. */ #include 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; expiry_add(&hub->expire_quickly, &lookup->expiry_entry, gtphub_now()); start_ares_query(lookup); return NULL; }