| /* 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); |
| 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_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; |
| |
| 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); |
| |
| 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; |
| } |