diff options
Diffstat (limited to 'openbsc/src/gprs/gtphub_ext.c')
-rw-r--r-- | openbsc/src/gprs/gtphub_ext.c | 174 |
1 files changed, 150 insertions, 24 deletions
diff --git a/openbsc/src/gprs/gtphub_ext.c b/openbsc/src/gprs/gtphub_ext.c index 0a4164c73..d739614ca 100644 --- a/openbsc/src/gprs/gtphub_ext.c +++ b/openbsc/src/gprs/gtphub_ext.c @@ -24,35 +24,161 @@ */ #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); -#define __llist_first(head) (((head)->next == (head)) ? NULL : (head)->next) -#define llist_first(head, type, entry) llist_entry(__llist_first(head), type, entry) +static void ggsn_lookup_cb(void *arg, int status, int timeouts, struct hostent *hostent) +{ + struct ggsn_lookup *lookup = arg; + + 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.\n"); + 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; + } -int gtphub_resolve_ggsn_addr(struct gtphub *hub, - struct osmo_sockaddr *result, - struct gtp_packet_desc *p) + memcpy(&resolved_addr.buf, addr0, hostent->h_length); + resolved_addr.len = hostent->h_length; + + gtphub_resolved_ggsn(lookup->hub, lookup->apn_oi_str, &resolved_addr, + gtphub_now()); + +remove_from_queue: + expiring_item_del(&lookup->expiry_entry); +} + +static void make_addr_str(struct ggsn_lookup *lookup) { - /* TODO This is just hardcodedly returning the first known address. - * Should resolve from actual subscriber data. */ - struct gtphub_peer *peer = llist_first(&hub->to_ggsns[GTPH_PLANE_CTRL].peers, - struct gtphub_peer, entry); - if (!peer) - return -1; - - struct gtphub_peer_addr *pa = llist_first(&peer->addresses, - struct gtphub_peer_addr, entry); - if (!pa) - return -1; - - struct gtphub_peer_port *pp = llist_first(&pa->ports, - struct gtphub_peer_port, entry); - if (!pp) - return -1; - - *result = pp->sa; - return 0; + 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\n", lookup->apn_oi_str); + + 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 *ggsn; + ggsn = container_of(expi, struct ggsn_lookup, expiry_entry); + + ggsn->expiry_entry.del_cb = 0; + expiring_item_del(expi); + + llist_del(&ggsn->entry); + talloc_free(ggsn); +} + +struct gtphub_peer_port *gtphub_resolve_ggsn_addr(struct gtphub *hub, + const char *imsi_str, + const char *apn_ni_str) +{ + struct ggsn_lookup *lookup = talloc_zero(osmo_gtphub_ctx, struct ggsn_lookup); + + 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) { + /* 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) { + /* 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. */ + + 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); + + return NULL; +} |