/* GTP Hub Implementation */ /* (C) 2015 by sysmocom s.f.m.c. GmbH * All Rights Reserved * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include static const int GTPH_GC_TICK_SECONDS = 1; void *osmo_gtphub_ctx; /* Convenience makro, note: only within this C file. */ #define LOG(level, fmt, args...) \ LOGP(DGTPHUB, level, fmt, ##args) #define ZERO_STRUCT(struct_pointer) memset(struct_pointer, '\0', \ sizeof(*(struct_pointer))) /* TODO move this to osmocom/core/select.h ? */ typedef int (*osmo_fd_cb_t)(struct osmo_fd *fd, unsigned int what); /* TODO move this to osmocom/core/linuxlist.h ? */ #define __llist_first(head) (((head)->next == (head)) ? NULL : (head)->next) #define llist_first(head, type, entry) \ llist_entry(__llist_first(head), type, entry) #define __llist_last(head) (((head)->next == (head)) ? NULL : (head)->prev) #define llist_last(head, type, entry) \ llist_entry(__llist_last(head), type, entry) /* TODO move GTP header stuff to openggsn/gtp/ ? See gtp_decaps*() */ enum gtp_rc { GTP_RC_UNKNOWN = 0, GTP_RC_TINY = 1, /* no IEs (like ping/pong) */ GTP_RC_PDU_C = 2, /* a real packet with IEs */ GTP_RC_PDU_U = 3, /* a real packet with User data */ GTP_RC_TOOSHORT = -1, GTP_RC_UNSUPPORTED_VERSION = -2, GTP_RC_INVALID_IE = -3, }; struct gtp_packet_desc { union gtp_packet *data; int data_len; int header_len; int version; uint8_t type; uint16_t seq; uint32_t header_tei_rx; uint32_t header_tei; int rc; /* enum gtp_rc */ unsigned int plane_idx; unsigned int side_idx; struct gtphub_tunnel *tun; time_t timestamp; union gtpie_member *ie[GTPIE_SIZE]; }; struct pending_delete { struct llist_head entry; struct expiring_item expiry_entry; struct gtphub_tunnel *tun; uint8_t teardown_ind; uint8_t nsapi; }; /* counters */ enum gtphub_counters_io { GTPH_CTR_PKTS_IN = 0, GTPH_CTR_PKTS_OUT, GTPH_CTR_BYTES_IN, GTPH_CTR_BYTES_OUT }; static const struct rate_ctr_desc gtphub_counters_io_desc[] = { { "packets.in", "Packets ( In)" }, { "packets.out", "Packets (Out)" }, { "bytes.in", "Bytes ( In)" }, { "bytes.out", "Bytes (Out)" }, }; static const struct rate_ctr_group_desc gtphub_ctrg_io_desc = { .group_name_prefix = "gtphub.bind", .group_description = "I/O Statistics", .num_ctr = ARRAY_SIZE(gtphub_counters_io_desc), .ctr_desc = gtphub_counters_io_desc, .class_id = OSMO_STATS_CLASS_GLOBAL, }; void gsn_addr_copy(struct gsn_addr *gsna, const struct gsn_addr *src) { *gsna = *src; } int gsn_addr_from_sockaddr(struct gsn_addr *gsna, uint16_t *port, const struct osmo_sockaddr *sa) { char addr_str[256]; char port_str[6]; if (osmo_sockaddr_to_strs(addr_str, sizeof(addr_str), port_str, sizeof(port_str), sa, (NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { return -1; } if (port) *port = atoi(port_str); return gsn_addr_from_str(gsna, addr_str); } int gsn_addr_from_str(struct gsn_addr *gsna, const char *numeric_addr_str) { if ((!gsna) || (!numeric_addr_str)) return -1; int af = AF_INET; gsna->len = 4; const char *pos = numeric_addr_str; for (; *pos; pos++) { if (*pos == ':') { af = AF_INET6; gsna->len = 16; break; } } int rc = inet_pton(af, numeric_addr_str, gsna->buf); if (rc != 1) { LOG(LOGL_ERROR, "Cannot resolve numeric address: '%s'\n", numeric_addr_str); return -1; } return 0; } const char *gsn_addr_to_str(const struct gsn_addr *gsna) { static char buf[INET6_ADDRSTRLEN + 1]; return gsn_addr_to_strb(gsna, buf, sizeof(buf)); } const char *gsn_addr_to_strb(const struct gsn_addr *gsna, char *strbuf, int strbuf_len) { int af; switch (gsna->len) { case 4: af = AF_INET; break; case 16: af = AF_INET6; break; default: return NULL; } const char *r = inet_ntop(af, gsna->buf, strbuf, strbuf_len); if (!r) { LOG(LOGL_ERROR, "Cannot convert gsn_addr to string:" " %s: len=%d, buf=%s\n", strerror(errno), (int)gsna->len, osmo_hexdump(gsna->buf, sizeof(gsna->buf))); } return r; } int gsn_addr_same(const struct gsn_addr *a, const struct gsn_addr *b) { if (a == b) return 1; if ((!a) || (!b)) return 0; if (a->len != b->len) return 0; return (memcmp(a->buf, b->buf, a->len) == 0)? 1 : 0; } static int gsn_addr_get(struct gsn_addr *gsna, const struct gtp_packet_desc *p, int idx) { if (p->rc != GTP_RC_PDU_C) return -1; unsigned int len; /* gtpie.h fails to declare gtpie_gettlv()'s first arg as const. */ if (gtpie_gettlv((union gtpie_member**)p->ie, GTPIE_GSN_ADDR, idx, &len, gsna->buf, sizeof(gsna->buf)) != 0) return -1; gsna->len = len; return 0; } static int gsn_addr_put(const struct gsn_addr *gsna, struct gtp_packet_desc *p, int idx) { if (p->rc != GTP_RC_PDU_C) return -1; int ie_idx; ie_idx = gtpie_getie(p->ie, GTPIE_GSN_ADDR, idx); if (ie_idx < 0) return -1; struct gtpie_tlv *ie = &p->ie[ie_idx]->tlv; int ie_l = ntoh16(ie->l); if (ie_l != gsna->len) { LOG(LOGL_ERROR, "Not implemented:" " replace an IE address of different size:" " replace %d with %d\n", (int)ie_l, (int)gsna->len); return -1; } memcpy(ie->v, gsna->buf, (int)ie_l); return 0; } /* Validate GTP version 0 data; analogous to validate_gtp1_header(), see there. */ void validate_gtp0_header(struct gtp_packet_desc *p) { const struct gtp0_header *pheader = &(p->data->gtp0.h); p->rc = GTP_RC_UNKNOWN; p->header_len = 0; OSMO_ASSERT(p->data_len >= 1); OSMO_ASSERT(p->version == 0); if (p->data_len < GTP0_HEADER_SIZE) { LOG(LOGL_ERROR, "GTP0 packet too short: %d\n", p->data_len); p->rc = GTP_RC_TOOSHORT; return; } p->type = ntoh8(pheader->type); p->seq = ntoh16(pheader->seq); p->header_tei_rx = 0; /* TODO */ p->header_tei = p->header_tei_rx; if (p->data_len == GTP0_HEADER_SIZE) { p->rc = GTP_RC_TINY; p->header_len = GTP0_HEADER_SIZE; return; } /* Check packet length field versus length of packet */ if (p->data_len != (ntoh16(pheader->length) + GTP0_HEADER_SIZE)) { LOG(LOGL_ERROR, "GTP packet length field (%d + %d) does not" " match actual length (%d)\n", GTP0_HEADER_SIZE, (int)ntoh16(pheader->length), p->data_len); p->rc = GTP_RC_TOOSHORT; return; } LOG(LOGL_DEBUG, "GTP v0 TID = %" PRIu64 "\n", pheader->tid); p->header_len = GTP0_HEADER_SIZE; p->rc = GTP_RC_PDU_C; } /* Validate GTP version 1 data, and update p->rc with the result, as well as * p->header_len in case of a valid header. */ void validate_gtp1_header(struct gtp_packet_desc *p) { const struct gtp1_header_long *pheader = &(p->data->gtp1l.h); p->rc = GTP_RC_UNKNOWN; p->header_len = 0; OSMO_ASSERT(p->data_len >= 1); OSMO_ASSERT(p->version == 1); if ((p->data_len < GTP1_HEADER_SIZE_LONG) && (p->data_len != GTP1_HEADER_SIZE_SHORT)){ LOG(LOGL_ERROR, "GTP packet too short: %d\n", p->data_len); p->rc = GTP_RC_TOOSHORT; return; } p->type = ntoh8(pheader->type); p->header_tei_rx = ntoh32(pheader->tei); p->header_tei = p->header_tei_rx; p->seq = ntoh16(pheader->seq); LOG(LOGL_DEBUG, "| GTPv1\n"); LOG(LOGL_DEBUG, "| type = %" PRIu8 " 0x%02" PRIx8 "\n", p->type, p->type); LOG(LOGL_DEBUG, "| length = %" PRIu16 " 0x%04" PRIx16 "\n", ntoh16(pheader->length), ntoh16(pheader->length)); LOG(LOGL_DEBUG, "| TEI = %" PRIu32 " 0x%08" PRIx32 "\n", p->header_tei_rx, p->header_tei_rx); LOG(LOGL_DEBUG, "| seq = %" PRIu16 " 0x%04" PRIx16 "\n", p->seq, p->seq); LOG(LOGL_DEBUG, "| npdu = %" PRIu8 " 0x%02" PRIx8 "\n", pheader->npdu, pheader->npdu); LOG(LOGL_DEBUG, "| next = %" PRIu8 " 0x%02" PRIx8 "\n", pheader->next, pheader->next); if (p->data_len <= GTP1_HEADER_SIZE_LONG) { p->rc = GTP_RC_TINY; p->header_len = GTP1_HEADER_SIZE_SHORT; return; } /* Check packet length field versus length of packet */ int announced_len = ntoh16(pheader->length) + GTP1_HEADER_SIZE_SHORT; if (p->data_len != announced_len) { LOG(LOGL_ERROR, "GTP packet length field (%d + %d) does not" " match actual length (%d)\n", GTP1_HEADER_SIZE_SHORT, (int)ntoh16(pheader->length), p->data_len); p->rc = GTP_RC_TOOSHORT; return; } p->rc = GTP_RC_PDU_C; p->header_len = GTP1_HEADER_SIZE_LONG; } /* Examine whether p->data of size p->data_len has a valid GTP header. Set * p->version, p->rc and p->header_len. On error, p->rc <= 0 (see enum * gtp_rc). p->data must point at a buffer with p->data_len set. */ void validate_gtp_header(struct gtp_packet_desc *p) { p->rc = GTP_RC_UNKNOWN; /* Need at least 1 byte in order to check version */ if (p->data_len < 1) { LOG(LOGL_ERROR, "Discarding packet - too small: %d\n", p->data_len); p->rc = GTP_RC_TOOSHORT; return; } p->version = p->data->flags >> 5; switch (p->version) { case 0: validate_gtp0_header(p); break; case 1: validate_gtp1_header(p); break; default: LOG(LOGL_ERROR, "Unsupported GTP version: %d\n", p->version); p->rc = GTP_RC_UNSUPPORTED_VERSION; break; } } /* Return the value of the i'th IMSI IEI by copying to *imsi. * The first IEI is reached by passing i = 0. * imsi must point at allocated space of (at least) 8 bytes. * Return 1 on success, or 0 if not found. */ static int get_ie_imsi(union gtpie_member *ie[], int i, uint8_t *imsi) { return gtpie_gettv0(ie, GTPIE_IMSI, i, imsi, 8) == 0; } /* Analogous to get_ie_imsi(). nsapi must point at a single uint8_t. */ static int get_ie_nsapi(union gtpie_member *ie[], int i, uint8_t *nsapi) { return gtpie_gettv1(ie, GTPIE_NSAPI, i, nsapi) == 0; } static char imsi_digit_to_char(uint8_t nibble) { nibble &= 0x0f; if (nibble > 9) return (nibble == 0x0f) ? '\0' : '?'; return '0' + nibble; } /* Return a human readable IMSI string, in a static buffer. * imsi must point at 8 octets of IMSI IE encoded IMSI data. */ static int imsi_to_str(uint8_t *imsi, const char **imsi_str) { static char str[17]; int i; for (i = 0; i < 8; i++) { char c; c = imsi_digit_to_char(imsi[i]); if (c == '?') return -1; str[2*i] = c; c = imsi_digit_to_char(imsi[i] >> 4); if (c == '?') return -1; str[2*i + 1] = c; } str[16] = '\0'; *imsi_str = str; return 1; } /* Return 0 if not present, 1 if present and decoded successfully, -1 if * present but cannot be decoded. */ static int get_ie_imsi_str(union gtpie_member *ie[], int i, const char **imsi_str) { uint8_t imsi_buf[8]; if (!get_ie_imsi(ie, i, imsi_buf)) return 0; return imsi_to_str(imsi_buf, imsi_str); } /* Return 0 if not present, 1 if present and decoded successfully, -1 if * present but cannot be decoded. */ static int get_ie_apn_str(union gtpie_member *ie[], const char **apn_str) { static char apn_buf[GSM_APN_LENGTH]; unsigned int len; if (gtpie_gettlv(ie, GTPIE_APN, 0, &len, apn_buf, sizeof(apn_buf)) != 0) return 0; if (len < 2) { LOG(LOGL_ERROR, "APN IE: invalid length: %d\n", (int)len); return -1; } if (len > (sizeof(apn_buf) - 1)) len = sizeof(apn_buf) - 1; apn_buf[len] = '\0'; *apn_str = gprs_apn_to_str(apn_buf, (uint8_t*)apn_buf, len); if (!(*apn_str)) { LOG(LOGL_ERROR, "APN IE: present but cannot be decoded: %s\n", osmo_hexdump((uint8_t*)apn_buf, len)); return -1; } return 1; } /* Validate header, and index information elements. Write decoded packet * information to *res. res->data will point at the given data buffer. On * error, p->rc is set <= 0 (see enum gtp_rc). */ static void gtp_decode(const uint8_t *data, int data_len, unsigned int from_side_idx, unsigned int from_plane_idx, struct gtp_packet_desc *res, time_t now) { ZERO_STRUCT(res); res->data = (union gtp_packet*)data; res->data_len = data_len; res->side_idx = from_side_idx; res->plane_idx = from_plane_idx; res->timestamp = now; validate_gtp_header(res); if (res->rc <= 0) return; LOG(LOGL_DEBUG, "Valid GTP header (v%d)\n", res->version); if (from_plane_idx == GTPH_PLANE_USER) { res->rc = GTP_RC_PDU_U; return; } if (res->rc != GTP_RC_PDU_C) { LOG(LOGL_DEBUG, "no IEs in this GTP packet\n"); return; } if (gtpie_decaps(res->ie, res->version, (void*)(data + res->header_len), res->data_len - res->header_len) != 0) { res->rc = GTP_RC_INVALID_IE; LOG(LOGL_ERROR, "INVALID: cannot decode IEs." " Dropping GTP packet.\n"); return; } #if 1 /* TODO if () { ... (waiting for a commit from jerlbeck) */ int i; for (i = 0; i < 10; i++) { const char *imsi; if (get_ie_imsi_str(res->ie, i, &imsi) < 1) break; LOG(LOGL_DEBUG, "| IMSI %s\n", imsi); } for (i = 0; i < 10; i++) { uint8_t nsapi; if (!get_ie_nsapi(res->ie, i, &nsapi)) break; LOG(LOGL_DEBUG, "| NSAPI %d\n", (int)nsapi); } for (i = 0; i < 2; i++) { struct gsn_addr addr; if (gsn_addr_get(&addr, res, i) == 0) LOG(LOGL_DEBUG, "| addr %s\n", gsn_addr_to_str(&addr)); } for (i = 0; i < 10; i++) { uint32_t tei; if (gtpie_gettv4(res->ie, GTPIE_TEI_DI, i, &tei) != 0) break; LOG(LOGL_DEBUG, "| TEI DI (USER) %" PRIu32 " 0x%08" PRIx32 "\n", tei, tei); } for (i = 0; i < 10; i++) { uint32_t tei; if (gtpie_gettv4(res->ie, GTPIE_TEI_C, i, &tei) != 0) break; LOG(LOGL_DEBUG, "| TEI (CTRL) %" PRIu32 " 0x%08" PRIx32 "\n", tei, tei); } #endif } /* expiry */ void expiry_init(struct expiry *exq, int expiry_in_seconds) { ZERO_STRUCT(exq); exq->expiry_in_seconds = expiry_in_seconds; INIT_LLIST_HEAD(&exq->items); } void expiry_add(struct expiry *exq, struct expiring_item *item, time_t now) { item->expiry = now + exq->expiry_in_seconds; OSMO_ASSERT(llist_empty(&exq->items) || (item->expiry >= llist_last(&exq->items, struct expiring_item, entry)->expiry)); /* Add/move to the tail to always sort by expiry, ascending. */ llist_del(&item->entry); llist_add_tail(&item->entry, &exq->items); } int expiry_tick(struct expiry *exq, time_t now) { int expired = 0; struct expiring_item *m, *n; llist_for_each_entry_safe(m, n, &exq->items, entry) { if (m->expiry <= now) { expiring_item_del(m); expired ++; } else { /* The items are added sorted by expiry. So when we hit * an unexpired entry, only more unexpired ones will * follow. */ break; } } return expired; } void expiry_clear(struct expiry *exq) { struct expiring_item *m, *n; llist_for_each_entry_safe(m, n, &exq->items, entry) { expiring_item_del(m); } } void expiring_item_init(struct expiring_item *item) { ZERO_STRUCT(item); INIT_LLIST_HEAD(&item->entry); } void expiring_item_del(struct expiring_item *item) { OSMO_ASSERT(item); llist_del(&item->entry); INIT_LLIST_HEAD(&item->entry); if (item->del_cb) { /* avoid loops */ del_cb_t del_cb = item->del_cb; item->del_cb = 0; (del_cb)(item); } } /* nr_map, nr_pool */ void nr_pool_init(struct nr_pool *pool, nr_t nr_min, nr_t nr_max) { *pool = (struct nr_pool){ .nr_min = nr_min, .nr_max = nr_max, .last_nr = nr_max }; } nr_t nr_pool_next(struct nr_pool *pool) { if (pool->last_nr >= pool->nr_max) pool->last_nr = pool->nr_min; else pool->last_nr ++; return pool->last_nr; } void nr_map_init(struct nr_map *map, struct nr_pool *pool, struct expiry *exq) { ZERO_STRUCT(map); map->pool = pool; map->add_items_to_expiry = exq; INIT_LLIST_HEAD(&map->mappings); } void nr_mapping_init(struct nr_mapping *m) { ZERO_STRUCT(m); INIT_LLIST_HEAD(&m->entry); expiring_item_init(&m->expiry_entry); } void nr_map_add(struct nr_map *map, struct nr_mapping *mapping, time_t now) { /* Generate a mapped number */ mapping->repl = nr_pool_next(map->pool); /* Add to the tail to always yield a list sorted by expiry, in * ascending order. */ llist_add_tail(&mapping->entry, &map->mappings); nr_map_refresh(map, mapping, now); } void nr_map_refresh(struct nr_map *map, struct nr_mapping *mapping, time_t now) { if (!map->add_items_to_expiry) return; expiry_add(map->add_items_to_expiry, &mapping->expiry_entry, now); } void nr_map_clear(struct nr_map *map) { struct nr_mapping *m; struct nr_mapping *n; llist_for_each_entry_safe(m, n, &map->mappings, entry) { nr_mapping_del(m); } } int nr_map_empty(const struct nr_map *map) { return llist_empty(&map->mappings); } struct nr_mapping *nr_map_get(const struct nr_map *map, void *origin, nr_t nr_orig) { struct nr_mapping *mapping; llist_for_each_entry(mapping, &map->mappings, entry) { if ((mapping->origin == origin) && (mapping->orig == nr_orig)) return mapping; } /* Not found. */ return NULL; } struct nr_mapping *nr_map_get_inv(const struct nr_map *map, nr_t nr_repl) { struct nr_mapping *mapping; llist_for_each_entry(mapping, &map->mappings, entry) { if (mapping->repl == nr_repl) { return mapping; } } /* Not found. */ return NULL; } void nr_mapping_del(struct nr_mapping *mapping) { OSMO_ASSERT(mapping); llist_del(&mapping->entry); INIT_LLIST_HEAD(&mapping->entry); expiring_item_del(&mapping->expiry_entry); } /* gtphub */ const char* const gtphub_plane_idx_names[GTPH_PLANE_N] = { "CTRL", "USER", }; const uint16_t gtphub_plane_idx_default_port[GTPH_PLANE_N] = { 2123, 2152, }; const char* const gtphub_side_idx_names[GTPH_SIDE_N] = { "SGSN", "GGSN", }; time_t gtphub_now(void) { struct timespec now_tp; OSMO_ASSERT(clock_gettime(CLOCK_MONOTONIC, &now_tp) >= 0); return now_tp.tv_sec; } /* Remove a gtphub_peer from its list and free it. */ static void gtphub_peer_del(struct gtphub_peer *peer) { OSMO_ASSERT(llist_empty(&peer->addresses)); nr_map_clear(&peer->seq_map); llist_del(&peer->entry); talloc_free(peer); } static void gtphub_peer_addr_del(struct gtphub_peer_addr *pa) { OSMO_ASSERT(llist_empty(&pa->ports)); llist_del(&pa->entry); talloc_free(pa); } static void gtphub_peer_port_del(struct gtphub_peer_port *pp) { OSMO_ASSERT(pp->ref_count == 0); llist_del(&pp->entry); rate_ctr_group_free(pp->counters_io); talloc_free(pp); } /* From the information in the gtp_packet_desc, return the address of a GGSN. * Return -1 on error. */ static int gtphub_resolve_ggsn(struct gtphub *hub, struct gtp_packet_desc *p, struct gtphub_peer_port **pp); /* See gtphub_ext.c (wrapped by unit test) */ struct gtphub_peer_port *gtphub_resolve_ggsn_addr(struct gtphub *hub, const char *imsi_str, const char *apn_ni_str); int gtphub_ares_init(struct gtphub *hub); static void gtphub_zero(struct gtphub *hub) { ZERO_STRUCT(hub); INIT_LLIST_HEAD(&hub->ggsn_lookups); INIT_LLIST_HEAD(&hub->resolved_ggsns); } static int gtphub_sock_init(struct osmo_fd *ofd, const struct gtphub_cfg_addr *addr, osmo_fd_cb_t cb, void *data, int ofd_id) { if (!addr->addr_str) { LOG(LOGL_FATAL, "Cannot bind: empty address.\n"); return -1; } if (!addr->port) { LOG(LOGL_FATAL, "Cannot bind: zero port not permitted.\n"); return -1; } ofd->when = BSC_FD_READ; ofd->cb = cb; ofd->data = data; ofd->priv_nr = ofd_id; int rc; rc = osmo_sock_init_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, addr->addr_str, addr->port, OSMO_SOCK_F_BIND); if (rc < 1) { LOG(LOGL_FATAL, "Cannot bind to %s port %d (rc %d)\n", addr->addr_str, (int)addr->port, rc); return -1; } return 0; } static void gtphub_sock_close(struct osmo_fd *ofd) { close(ofd->fd); osmo_fd_unregister(ofd); ofd->cb = NULL; } static void gtphub_bind_init(struct gtphub_bind *b) { ZERO_STRUCT(b); INIT_LLIST_HEAD(&b->peers); b->counters_io = rate_ctr_group_alloc(osmo_gtphub_ctx, >phub_ctrg_io_desc, 0); OSMO_ASSERT(b->counters_io); } static int gtphub_bind_start(struct gtphub_bind *b, const struct gtphub_cfg_bind *cfg, osmo_fd_cb_t cb, void *cb_data, unsigned int ofd_id) { LOG(LOGL_DEBUG, "Starting bind %s\n", b->label); if (gsn_addr_from_str(&b->local_addr, cfg->bind.addr_str) != 0) { LOG(LOGL_FATAL, "Invalid bind address for %s: %s\n", b->label, cfg->bind.addr_str); return -1; } if (gtphub_sock_init(&b->ofd, &cfg->bind, cb, cb_data, ofd_id) != 0) { LOG(LOGL_FATAL, "Cannot bind for %s: %s\n", b->label, cfg->bind.addr_str); return -1; } b->local_port = cfg->bind.port; return 0; } static void gtphub_bind_free(struct gtphub_bind *b) { OSMO_ASSERT(llist_empty(&b->peers)); rate_ctr_group_free(b->counters_io); } static void gtphub_bind_stop(struct gtphub_bind *b) { gtphub_sock_close(&b->ofd); gtphub_bind_free(b); } /* Recv datagram from from->fd, write sender's address to *from_addr. * Return the number of bytes read, zero on error. */ static int gtphub_read(const struct osmo_fd *from, struct osmo_sockaddr *from_addr, uint8_t *buf, size_t buf_len) { OSMO_ASSERT(from_addr); /* recvfrom requires the available length set in *from_addr_len. */ from_addr->l = sizeof(from_addr->a); errno = 0; ssize_t received = recvfrom(from->fd, buf, buf_len, 0, (struct sockaddr*)&from_addr->a, &from_addr->l); /* TODO use recvmsg and get a MSG_TRUNC flag to make sure the message * is not truncated. Then maybe reduce buf's size. */ if (received <= 0) { LOG((errno == EAGAIN? LOGL_DEBUG : LOGL_ERROR), "error: %s\n", strerror(errno)); return 0; } LOG(LOGL_DEBUG, "Received %d bytes from %s: %s\n", (int)received, osmo_sockaddr_to_str(from_addr), osmo_hexdump(buf, received)); return received; } inline void gtphub_port_ref_count_inc(struct gtphub_peer_port *pp) { OSMO_ASSERT(pp); OSMO_ASSERT(pp->ref_count < UINT_MAX); pp->ref_count++; } inline void gtphub_port_ref_count_dec(struct gtphub_peer_port *pp) { OSMO_ASSERT(pp); OSMO_ASSERT(pp->ref_count > 0); pp->ref_count--; } inline void set_seq(struct gtp_packet_desc *p, uint16_t seq) { OSMO_ASSERT(p->version == 1); p->data->gtp1l.h.seq = hton16(seq); p->seq = seq; } inline void set_tei(struct gtp_packet_desc *p, uint32_t tei) { OSMO_ASSERT(p->version == 1); p->data->gtp1l.h.tei = hton32(tei); p->header_tei = tei; } static void gtphub_mapping_del_cb(struct expiring_item *expi); static struct nr_mapping *gtphub_mapping_new() { struct nr_mapping *nrm; nrm = talloc_zero(osmo_gtphub_ctx, struct nr_mapping); OSMO_ASSERT(nrm); nr_mapping_init(nrm); nrm->expiry_entry.del_cb = gtphub_mapping_del_cb; return nrm; } #define APPEND(args...) \ l = snprintf(pos, left, args); \ pos += l; \ left -= l static const char *gtphub_tunnel_side_str(struct gtphub_tunnel *tun, int side_idx) { static char buf[256]; char *pos = buf; int left = sizeof(buf); int l; struct gtphub_tunnel_endpoint *c, *u; c = &tun->endpoint[side_idx][GTPH_PLANE_CTRL]; u = &tun->endpoint[side_idx][GTPH_PLANE_USER]; /* print both only if they differ. */ if (!c->peer) { APPEND("(uninitialized)"); } else { APPEND("%s", gsn_addr_to_str(&c->peer->peer_addr->addr)); } if (!u->peer) { if (c->peer) { APPEND("/(uninitialized)"); } } else if ((!c->peer) || (!gsn_addr_same(&u->peer->peer_addr->addr, &c->peer->peer_addr->addr))) { APPEND("/%s", gsn_addr_to_str(&u->peer->peer_addr->addr)); } APPEND(" (TEI C=%x U=%x)", c->tei_orig, u->tei_orig); return buf; } const char *gtphub_tunnel_str(struct gtphub_tunnel *tun) { static char buf[512]; char *pos = buf; int left = sizeof(buf); int l; APPEND("TEI=%x: ", tun->tei_repl); APPEND("%s", gtphub_tunnel_side_str(tun, GTPH_SIDE_SGSN)); APPEND(" <-> %s", gtphub_tunnel_side_str(tun, GTPH_SIDE_GGSN)); return buf; } #undef APPEND void gtphub_tunnel_endpoint_set_peer(struct gtphub_tunnel_endpoint *te, struct gtphub_peer_port *pp) { if (te->peer) gtphub_port_ref_count_dec(te->peer); te->peer = pp; if (te->peer) gtphub_port_ref_count_inc(te->peer); } int gtphub_tunnel_complete(struct gtphub_tunnel *tun) { if (!tun) return 0; if (!tun->tei_repl) return 0; int side_idx; int plane_idx; for_each_side_and_plane(side_idx, plane_idx) { struct gtphub_tunnel_endpoint *te = &tun->endpoint[side_idx][plane_idx]; if (!(te->peer && te->tei_orig)) return 0; } return 1; } static void gtphub_tunnel_del_cb(struct expiring_item *expi) { struct gtphub_tunnel *tun = container_of(expi, struct gtphub_tunnel, expiry_entry); LOG(LOGL_DEBUG, "expired: %s\n", gtphub_tunnel_str(tun)); llist_del(&tun->entry); INIT_LLIST_HEAD(&tun->entry); /* mark unused */ expi->del_cb = 0; /* avoid recursion loops */ expiring_item_del(&tun->expiry_entry); /* usually already done, but make sure. */ int side_idx; int plane_idx; for_each_side_and_plane(side_idx, plane_idx) { struct gtphub_tunnel_endpoint *te = &tun->endpoint[side_idx][plane_idx]; /* clear ref count */ gtphub_tunnel_endpoint_set_peer(te, NULL); rate_ctr_group_free(te->counters_io); } talloc_free(tun); } static struct gtphub_tunnel *gtphub_tunnel_new() { struct gtphub_tunnel *tun; tun = talloc_zero(osmo_gtphub_ctx, struct gtphub_tunnel); OSMO_ASSERT(tun); INIT_LLIST_HEAD(&tun->entry); expiring_item_init(&tun->expiry_entry); int side_idx, plane_idx; for_each_side_and_plane(side_idx, plane_idx) { struct gtphub_tunnel_endpoint *te = &tun->endpoint[side_idx][plane_idx]; te->counters_io = rate_ctr_group_alloc(osmo_gtphub_ctx, >phub_ctrg_io_desc, 0); OSMO_ASSERT(te->counters_io); } tun->expiry_entry.del_cb = gtphub_tunnel_del_cb; return tun; } static const char *gtphub_peer_strb(struct gtphub_peer *peer, char *buf, int buflen) { if (llist_empty(&peer->addresses)) return "(addressless)"; struct gtphub_peer_addr *a = llist_first(&peer->addresses, struct gtphub_peer_addr, entry); return gsn_addr_to_strb(&a->addr, buf, buflen); } static const char *gtphub_port_strb(struct gtphub_peer_port *port, char *buf, int buflen) { if (!port) return "(null port)"; snprintf(buf, buflen, "%s port %d", gsn_addr_to_str(&port->peer_addr->addr), (int)port->port); return buf; } const char *gtphub_peer_str(struct gtphub_peer *peer) { static char buf[256]; return gtphub_peer_strb(peer, buf, sizeof(buf)); } const char *gtphub_port_str(struct gtphub_peer_port *port) { static char buf[256]; return gtphub_port_strb(port, buf, sizeof(buf)); } static const char *gtphub_port_str2(struct gtphub_peer_port *port) { static char buf[256]; return gtphub_port_strb(port, buf, sizeof(buf)); } static void gtphub_mapping_del_cb(struct expiring_item *expi) { expi->del_cb = 0; /* avoid recursion loops */ expiring_item_del(expi); /* usually already done, but make sure. */ struct nr_mapping *nrm = container_of(expi, struct nr_mapping, expiry_entry); llist_del(&nrm->entry); INIT_LLIST_HEAD(&nrm->entry); /* mark unused */ /* Just for log */ struct gtphub_peer_port *from = nrm->origin; OSMO_ASSERT(from); LOG(LOGL_DEBUG, "expired: %d: nr mapping from %s: %u->%u\n", (int)nrm->expiry_entry.expiry, gtphub_port_str(from), (unsigned int)nrm->orig, (unsigned int)nrm->repl); gtphub_port_ref_count_dec(from); talloc_free(nrm); } static struct nr_mapping *gtphub_mapping_have(struct nr_map *map, struct gtphub_peer_port *from, nr_t orig_nr, time_t now) { struct nr_mapping *nrm; nrm = nr_map_get(map, from, orig_nr); if (!nrm) { nrm = gtphub_mapping_new(); nrm->orig = orig_nr; nrm->origin = from; nr_map_add(map, nrm, now); gtphub_port_ref_count_inc(from); LOG(LOGL_DEBUG, "peer %s: sequence map %d --> %d\n", gtphub_port_str(from), (int)(nrm->orig), (int)(nrm->repl)); } else { nr_map_refresh(map, nrm, now); } OSMO_ASSERT(nrm); return nrm; } static void gtphub_map_seq(struct gtp_packet_desc *p, struct gtphub_peer_port *from_port, struct gtphub_peer_port *to_port) { /* Store a mapping in to_peer's map, so when we later receive a GTP * packet back from to_peer, the seq nr can be unmapped back to its * origin (from_peer here). */ struct nr_mapping *nrm; nrm = gtphub_mapping_have(&to_port->peer_addr->peer->seq_map, from_port, p->seq, p->timestamp); /* Change the GTP packet to yield the new, mapped seq nr */ set_seq(p, nrm->repl); } static struct gtphub_peer_port *gtphub_unmap_seq(struct gtp_packet_desc *p, struct gtphub_peer_port *responding_port) { OSMO_ASSERT(p->version == 1); struct nr_mapping *nrm = nr_map_get_inv(&responding_port->peer_addr->peer->seq_map, p->seq); if (!nrm) return NULL; LOG(LOGL_DEBUG, "peer %p: sequence unmap %d <-- %d\n", nrm->origin, (int)(nrm->orig), (int)(nrm->repl)); set_seq(p, nrm->orig); return nrm->origin; } static int gtphub_check_mapped_tei(struct gtphub_tunnel *new_tun, struct gtphub_tunnel *iterated_tun, uint32_t *tei_min, uint32_t *tei_max) { if (!new_tun->tei_repl || !iterated_tun->tei_repl) return 1; *tei_min = (*tei_min < iterated_tun->tei_repl)? *tei_min : iterated_tun->tei_repl; *tei_max = (*tei_max > iterated_tun->tei_repl)? *tei_max : iterated_tun->tei_repl; if (new_tun->tei_repl != iterated_tun->tei_repl) return 1; /* new_tun->tei_repl is already taken. Try to find one out of the known * range. */ LOG(LOGL_DEBUG, "TEI replacement %d already taken.\n", new_tun->tei_repl); if ((*tei_max) < 0xffffffff) { (*tei_max)++; new_tun->tei_repl = *tei_max; LOG(LOGL_DEBUG, "Using TEI %d instead.\n", new_tun->tei_repl); return 1; } else if ((*tei_min) > 1) { (*tei_min)--; new_tun->tei_repl = *tei_min; LOG(LOGL_DEBUG, "Using TEI %d instead.\n", new_tun->tei_repl); return 1; } /* None seems to be available. */ return 0; } static int gtphub_check_reused_teis(struct gtphub *hub, struct gtphub_tunnel *new_tun) { uint32_t tei_min = 0xffffffff; uint32_t tei_max = 0; int side_idx; int plane_idx; struct gtphub_tunnel_endpoint *te; struct gtphub_tunnel_endpoint *te2; struct gtphub_tunnel *tun, *ntun; llist_for_each_entry_safe(tun, ntun, &hub->tunnels, entry) { if (tun == new_tun) continue; /* Check whether the GSN sent a TEI that it is reusing from a * previous tunnel. */ int tun_continue = 0; for_each_side(side_idx) { for_each_plane(plane_idx) { te = &tun->endpoint[side_idx][plane_idx]; te2 = &new_tun->endpoint[side_idx][plane_idx]; if ((te->tei_orig == 0) || (te->tei_orig != te2->tei_orig) || (!te->peer) || (!te2->peer) || !gsn_addr_same(&te->peer->peer_addr->addr, &te2->peer->peer_addr->addr)) continue; /* The peer is reusing a TEI that I believe to * be part of another tunnel. The other tunnel * must be stale, then. */ LOG(LOGL_NOTICE, "Expiring tunnel due to reused TEI:" " %s peer %s sent %s TEI %x," " previously used by tunnel %s...\n", gtphub_side_idx_names[side_idx], gtphub_port_str(te->peer), gtphub_plane_idx_names[plane_idx], te->tei_orig, gtphub_tunnel_str(tun)); LOG(LOGL_NOTICE, "...while establishing tunnel %s\n", gtphub_tunnel_str(new_tun)); expiring_item_del(&tun->expiry_entry); /* continue to find more matches. There shouldn't be * any, but let's make sure. However, tun is deleted, * so we need to skip to the next tunnel. */ tun_continue = 1; break; } if (tun_continue) break; } if (tun_continue) continue; /* Check whether the mapped TEI is already used by another * tunnel. */ if (!gtphub_check_mapped_tei(new_tun, tun, &tei_min, &tei_max)) { LOG(LOGL_ERROR, "No mapped TEI is readily available." " Searching for holes between occupied" " TEIs not implemented."); return 0; } } return 1; } static void gtphub_tunnel_refresh(struct gtphub *hub, struct gtphub_tunnel *tun, time_t now) { expiry_add(&hub->expire_slowly, &tun->expiry_entry, now); } static struct gtphub_tunnel_endpoint *gtphub_unmap_tei(struct gtphub *hub, struct gtp_packet_desc *p, struct gtphub_peer_port *from, struct gtphub_tunnel **unmapped_from_tun) { OSMO_ASSERT(from); int other_side = other_side_idx(p->side_idx); struct gtphub_tunnel *tun; llist_for_each_entry(tun, &hub->tunnels, entry) { struct gtphub_tunnel_endpoint *te_from = &tun->endpoint[p->side_idx][p->plane_idx]; struct gtphub_tunnel_endpoint *te_to = &tun->endpoint[other_side][p->plane_idx]; if ((tun->tei_repl == p->header_tei_rx) && te_from->peer && gsn_addr_same(&te_from->peer->peer_addr->addr, &from->peer_addr->addr)) { gtphub_tunnel_refresh(hub, tun, p->timestamp); if (unmapped_from_tun) *unmapped_from_tun = tun; return te_to; } } if (unmapped_from_tun) *unmapped_from_tun = NULL; return NULL; } static void gtphub_map_restart_counter(struct gtphub *hub, struct gtp_packet_desc *p) { if (p->rc != GTP_RC_PDU_C) return; int ie_idx; ie_idx = gtpie_getie(p->ie, GTPIE_RECOVERY, 0); if (ie_idx < 0) return; /* Always send gtphub's own restart counter */ p->ie[ie_idx]->tv1.v = hton8(hub->restart_counter); } static int gtphub_unmap_header_tei(struct gtphub_peer_port **to_port_p, struct gtphub_tunnel **unmapped_from_tun, struct gtphub *hub, struct gtp_packet_desc *p, struct gtphub_peer_port *from_port) { OSMO_ASSERT(p->version == 1); *to_port_p = NULL; if (unmapped_from_tun) *unmapped_from_tun = NULL; /* If the header's TEI is zero, no PDP context has been established * yet. If nonzero, a mapping should actually already exist for this * TEI, since it must have been announced in a PDP context creation. */ if (!p->header_tei_rx) return 0; /* to_peer has previously announced a TEI, which was stored and * mapped in a tunnel struct. */ struct gtphub_tunnel_endpoint *to; to = gtphub_unmap_tei(hub, p, from_port, unmapped_from_tun); if (!to) { LOG(LOGL_ERROR, "Received unknown TEI %" PRIx32 " from %s\n", p->header_tei_rx, gtphub_port_str(from_port)); return -1; } OSMO_ASSERT(*unmapped_from_tun); uint32_t unmapped_tei = to->tei_orig; set_tei(p, unmapped_tei); LOG(LOGL_DEBUG, "Unmapped TEI coming from: %s\n", gtphub_tunnel_str(*unmapped_from_tun)); /* May be NULL for an invalidated tunnel. */ *to_port_p = to->peer; return 0; } static int gtphub_handle_create_pdp_ctx(struct gtphub *hub, struct gtp_packet_desc *p, struct gtphub_peer_port *from_ctrl, struct gtphub_peer_port *to_ctrl) { int plane_idx; osmo_static_assert((GTPH_PLANE_CTRL == 0) && (GTPH_PLANE_USER == 1), plane_nrs_match_GSN_addr_IE_indices); struct gtphub_tunnel *tun = p->tun; if (p->type == GTP_CREATE_PDP_REQ) { if (p->side_idx != GTPH_SIDE_SGSN) { LOG(LOGL_ERROR, "Wrong side: Create PDP Context" " Request from the GGSN side: %s", gtphub_port_str(from_ctrl)); return -1; } if (tun) { LOG(LOGL_ERROR, "Not implemented: Received" " Create PDP Context Request for an already" " established tunnel:" " from %s, tunnel %s\n", gtphub_port_str(from_ctrl), gtphub_tunnel_str(p->tun)); return -1; } /* A new tunnel. */ p->tun = tun = gtphub_tunnel_new(); /* Create TEI mapping */ tun->tei_repl = nr_pool_next(&hub->tei_pool); llist_add(&tun->entry, &hub->tunnels); gtphub_tunnel_refresh(hub, tun, p->timestamp); /* The endpoint peers on this side (SGSN) will be set from IEs * below. Also set the GGSN Ctrl endpoint, for logging. */ gtphub_tunnel_endpoint_set_peer(&tun->endpoint[GTPH_SIDE_GGSN][GTPH_PLANE_CTRL], to_ctrl); } else if (p->type == GTP_CREATE_PDP_RSP) { if (p->side_idx != GTPH_SIDE_GGSN) { LOG(LOGL_ERROR, "Wrong side: Create PDP Context" " Response from the SGSN side: %s", gtphub_port_str(from_ctrl)); return -1; } /* The tunnel should already have been resolved from the header * TEI and be available in tun (== p->tun). Just fill in the * GSN Addresses below.*/ OSMO_ASSERT(tun); OSMO_ASSERT(tun->tei_repl == p->header_tei_rx); OSMO_ASSERT(to_ctrl); } uint8_t ie_type[] = { GTPIE_TEI_C, GTPIE_TEI_DI }; int ie_mandatory = (p->type == GTP_CREATE_PDP_REQ); unsigned int side_idx = p->side_idx; for (plane_idx = 0; plane_idx < 2; plane_idx++) { int rc; struct gsn_addr use_addr; uint16_t use_port; uint32_t tei_from_ie; int ie_idx; /* Fetch GSN Address and TEI from IEs. As ensured by above * static asserts, plane_idx corresponds to the GSN Address IE * index (the first one = 0 = ctrl, second one = 1 = user). */ rc = gsn_addr_get(&use_addr, p, plane_idx); if (rc) { LOG(LOGL_ERROR, "Cannot read %s GSN Address IE\n", gtphub_plane_idx_names[plane_idx]); return -1; } LOG(LOGL_DEBUG, "Read %s GSN addr %s (%d)\n", gtphub_plane_idx_names[plane_idx], gsn_addr_to_str(&use_addr), use_addr.len); ie_idx = gtpie_getie(p->ie, ie_type[plane_idx], 0); if (ie_idx < 0) { if (ie_mandatory) { LOG(LOGL_ERROR, "Create PDP Context message invalid:" " missing IE %d\n", (int)ie_type[plane_idx]); return -1; } tei_from_ie = 0; } else tei_from_ie = ntoh32(p->ie[ie_idx]->tv4.v); /* Make sure an entry for this peer address with default port * exists. * * Exception: if sgsn_use_sender is set, instead use the * sender's address and port for Ctrl -- the User port is not * known until the first User packet arrives. * * Note: doing this here is just an optimization, because * gtphub_handle_buf() has code to replace the tunnel * endpoints' addresses with the sender (needed for User * plane). We could just ignore sgsn_use_sender here. But if we * set up a default port here and replace it in * gtphub_handle_buf(), we'd be creating a peer port just to * expire it right away. */ if (hub->sgsn_use_sender && (side_idx == GTPH_SIDE_SGSN)) { gsn_addr_from_sockaddr(&use_addr, &use_port, &from_ctrl->sa); } else { use_port = gtphub_plane_idx_default_port[plane_idx]; } struct gtphub_peer_port *peer_from_ie; peer_from_ie = gtphub_port_have(hub, &hub->to_gsns[side_idx][plane_idx], &use_addr, use_port); gtphub_tunnel_endpoint_set_peer(&tun->endpoint[side_idx][plane_idx], peer_from_ie); if (!tei_from_ie && !tun->endpoint[side_idx][plane_idx].tei_orig) { LOG(LOGL_ERROR, "Create PDP Context message omits %s TEI, but" " no TEI has been announced for this tunnel: %s\n", gtphub_plane_idx_names[plane_idx], gtphub_tunnel_str(tun)); return -1; } if (tei_from_ie) { /* Replace TEI in GTP packet IE */ tun->endpoint[side_idx][plane_idx].tei_orig = tei_from_ie; p->ie[ie_idx]->tv4.v = hton32(tun->tei_repl); if (!gtphub_check_reused_teis(hub, tun)) { /* It's highly unlikely that all TEIs are * taken. But the code looking for an unused * TEI is, at the time of writing this comment, * not able to find gaps in the TEI space. To * explicitly alert the user of this problem, * rather abort than carry on. */ LOG(LOGL_FATAL, "TEI range exhausted. Cannot create TEI mapping, aborting.\n"); abort(); } } /* Replace the GSN address to reflect gtphub. */ rc = gsn_addr_put(&hub->to_gsns[other_side_idx(side_idx)][plane_idx].local_addr, p, plane_idx); if (rc) { LOG(LOGL_ERROR, "Cannot write %s GSN Address IE\n", gtphub_plane_idx_names[plane_idx]); return -1; } } if (p->type == GTP_CREATE_PDP_REQ) { LOG(LOGL_DEBUG, "New tunnel, first half: %s\n", gtphub_tunnel_str(tun)); } else if (p->type == GTP_CREATE_PDP_RSP) { LOG(LOGL_DEBUG, "New tunnel: %s\n", gtphub_tunnel_str(tun)); } return 0; } static void pending_delete_del_cb(struct expiring_item *expi) { struct pending_delete *pd; pd = container_of(expi, struct pending_delete, expiry_entry); llist_del(&pd->entry); INIT_LLIST_HEAD(&pd->entry); pd->expiry_entry.del_cb = 0; expiring_item_del(&pd->expiry_entry); talloc_free(pd); } static struct pending_delete *pending_delete_new(void) { struct pending_delete *pd = talloc_zero(osmo_gtphub_ctx, struct pending_delete); INIT_LLIST_HEAD(&pd->entry); expiring_item_init(&pd->expiry_entry); pd->expiry_entry.del_cb = pending_delete_del_cb; return pd; } static int gtphub_handle_delete_pdp_ctx(struct gtphub *hub, struct gtp_packet_desc *p, struct gtphub_peer_port *from_ctrl, struct gtphub_peer_port *to_ctrl) { struct gtphub_tunnel *known_tun = p->tun; if (p->type == GTP_DELETE_PDP_REQ) { if (!known_tun) { LOG(LOGL_ERROR, "Cannot find tunnel for Delete PDP Context Request.\n"); return -1; } /* Store the Delete Request until a successful Response is seen. */ uint8_t teardown_ind; uint8_t nsapi; if (gtpie_gettv1(p->ie, GTPIE_TEARDOWN, 0, &teardown_ind) != 0) { LOG(LOGL_ERROR, "Missing Teardown Ind IE in Delete PDP Context Request.\n"); return -1; } if (gtpie_gettv1(p->ie, GTPIE_NSAPI, 0, &nsapi) != 0) { LOG(LOGL_ERROR, "Missing NSAPI IE in Delete PDP Context Request.\n"); return -1; } struct pending_delete *pd = NULL; struct pending_delete *pdi = NULL; llist_for_each_entry(pdi, &hub->pending_deletes, entry) { if ((pdi->tun == known_tun) && (pdi->teardown_ind == teardown_ind) && (pdi->nsapi == nsapi)) { pd = pdi; break; } } if (!pd) { pd = pending_delete_new(); pd->tun = known_tun; pd->teardown_ind = teardown_ind; pd->nsapi = nsapi; LOG(LOGL_DEBUG, "Tunnel delete pending: %s\n", gtphub_tunnel_str(known_tun)); llist_add(&pd->entry, &hub->pending_deletes); } /* Add or refresh timeout. */ expiry_add(&hub->expire_quickly, &pd->expiry_entry, p->timestamp); /* If a pending_delete should expire before the response to * indicate success comes in, the responding peer will have the * tunnel deactivated, while the requesting peer gets no reply * and keeps the tunnel. The hope is that the requesting peer * will try again and get a useful response. */ } else if (p->type == GTP_DELETE_PDP_RSP) { /* Find the Delete Request for this Response. */ struct pending_delete *pd = NULL; struct pending_delete *pdi; llist_for_each_entry(pdi, &hub->pending_deletes, entry) { if (known_tun == pdi->tun) { pd = pdi; break; } } if (!pd) { LOG(LOGL_ERROR, "Delete PDP Context Response:" " Cannot find matching request."); /* If we delete the tunnel now, anyone can send a * Delete response to kill tunnels at will. */ return -1; } /* TODO handle teardown_ind and nsapi */ expiring_item_del(&pd->expiry_entry); uint8_t cause; if (gtpie_gettv1(p->ie, GTPIE_CAUSE, 0, &cause) != 0) { LOG(LOGL_ERROR, "Delete PDP Context Response:" " Missing Cause IE."); /* If we delete the tunnel now, at least one of the * peers may still think it is active. */ return -1; } if (cause != GTPCAUSE_ACC_REQ) { LOG(LOGL_NOTICE, "Delete PDP Context Response indicates failure;" "for %s\n", gtphub_tunnel_str(known_tun)); return -1; } LOG(LOGL_DEBUG, "Delete PDP Context: removing tunnel %s\n", gtphub_tunnel_str(known_tun)); p->tun = NULL; expiring_item_del(&known_tun->expiry_entry); } return 0; } static int gtphub_handle_update_pdp_ctx(struct gtphub *hub, struct gtp_packet_desc *p, struct gtphub_peer_port *from_ctrl, struct gtphub_peer_port *to_ctrl) { /* TODO */ return 0; } /* Read GSN address IEs from p, and make sure these peer addresses exist in * bind[plane_idx] with default ports, in their respective planes (both Ctrl * and User). Map TEIs announced in IEs, and write mapped TEIs in-place into * the packet p. */ static int gtphub_handle_pdp_ctx(struct gtphub *hub, struct gtp_packet_desc *p, struct gtphub_peer_port *from_ctrl, struct gtphub_peer_port *to_ctrl) { OSMO_ASSERT(p->plane_idx == GTPH_PLANE_CTRL); switch (p->type) { case GTP_CREATE_PDP_REQ: case GTP_CREATE_PDP_RSP: return gtphub_handle_create_pdp_ctx(hub, p, from_ctrl, to_ctrl); case GTP_DELETE_PDP_REQ: case GTP_DELETE_PDP_RSP: return gtphub_handle_delete_pdp_ctx(hub, p, from_ctrl, to_ctrl); case GTP_UPDATE_PDP_REQ: case GTP_UPDATE_PDP_RSP: return gtphub_handle_update_pdp_ctx(hub, p, from_ctrl, to_ctrl); default: /* Nothing to do for this message type. */ return 0; } } static int gtphub_send_del_pdp_ctx(struct gtphub *hub, struct gtphub_tunnel *tun, int to_side) { static uint8_t del_ctx_msg[16] = { 0x32, /* GTP v1 flags */ GTP_DELETE_PDP_REQ, 0x00, 0x08, /* Length in network byte order */ 0x00, 0x00, 0x00, 0x00, /* TEI to be replaced */ 0, 0, /* Seq, to be replaced */ 0, 0, /* no extensions */ 0x13, 0xff, /* 19: Teardown ind = 1 */ 0x14, 0 /* 20: NSAPI = 0 */ }; uint32_t *tei = (uint32_t*)&del_ctx_msg[4]; uint16_t *seq = (uint16_t*)&del_ctx_msg[8]; struct gtphub_tunnel_endpoint *te = &tun->endpoint[to_side][GTPH_PLANE_CTRL]; if (! te->peer) return 0; *tei = hton32(te->tei_orig); *seq = hton16(nr_pool_next(&te->peer->peer_addr->peer->seq_pool)); struct gtphub_bind *to_bind = &hub->to_gsns[to_side][GTPH_PLANE_CTRL]; int rc = gtphub_write(&to_bind->ofd, &te->peer->sa, del_ctx_msg, sizeof(del_ctx_msg)); if (rc != 0) { LOG(LOGL_ERROR, "Failed to send out-of-band Delete PDP Context Request to %s\n", gtphub_port_str(te->peer)); } return rc; } /* Tell all peers on the other end of tunnels that PDP contexts are void. */ static void gtphub_restarted(struct gtphub *hub, struct gtp_packet_desc *p, struct gtphub_peer_port *pp) { LOG(LOGL_DEBUG, "Peer has restarted: %s\n", gtphub_port_str(pp)); struct gtphub_tunnel *tun; llist_for_each_entry(tun, &hub->tunnels, entry) { int side_idx; for_each_side(side_idx) { struct gtphub_tunnel_endpoint *te = &tun->endpoint[side_idx][GTPH_PLANE_CTRL]; struct gtphub_tunnel_endpoint *te2 = &tun->endpoint[other_side_idx(side_idx)][GTPH_PLANE_CTRL]; if ((!te->peer) || (!te2->tei_orig) || (pp->peer_addr->peer != te->peer->peer_addr->peer)) continue; LOG(LOGL_DEBUG, "Deleting tunnel due to peer restart: %s\n", gtphub_tunnel_str(tun)); /* Send a Delete PDP Context Request to the * peer on the other side, remember the pending * delete and wait for the response to delete * the tunnel. Clear this side of the tunnel to * make sure it isn't used. * * Should the delete message send fail, or if no * response is received, this tunnel will expire. If * its TEIs come up in a new PDP Context Request, it * will be removed. If messages for this tunnel should * come in (from the not restarted side), they will be * dropped because the tunnel is rendered unusable. */ gtphub_send_del_pdp_ctx(hub, tun, other_side_idx(side_idx)); struct pending_delete *pd; pd = pending_delete_new(); pd->tun = tun; pd->teardown_ind = 0xff; pd->nsapi = 0; llist_add(&pd->entry, &hub->pending_deletes); expiry_add(&hub->expire_quickly, &pd->expiry_entry, p->timestamp); gtphub_tunnel_endpoint_set_peer(&tun->endpoint[side_idx][GTPH_PLANE_CTRL], NULL); gtphub_tunnel_endpoint_set_peer(&tun->endpoint[side_idx][GTPH_PLANE_USER], NULL); } } } static int get_restart_count(struct gtp_packet_desc *p) { int ie_idx; ie_idx = gtpie_getie(p->ie, GTPIE_RECOVERY, 0); if (ie_idx < 0) return -1; return ntoh8(p->ie[ie_idx]->tv1.v); } static void gtphub_check_restart_counter(struct gtphub *hub, struct gtp_packet_desc *p, struct gtphub_peer_port *from) { /* If the peer is sending a Recovery IE (7.7.11) with a restart counter * that doesn't match the peer's previously sent restart counter, clear * that peer and cancel PDP contexts. */ int restart = get_restart_count(p); if ((restart < 0) || (restart > 255)) return; if ((from->last_restart_count >= 0) && (from->last_restart_count <= 255)) { if (from->last_restart_count != restart) { gtphub_restarted(hub, p, from); } } from->last_restart_count = restart; } static int from_sgsns_read_cb(struct osmo_fd *from_sgsns_ofd, unsigned int what) { unsigned int plane_idx = from_sgsns_ofd->priv_nr; OSMO_ASSERT(plane_idx < GTPH_PLANE_N); LOG(LOGL_DEBUG, "=== reading from SGSN (%s)\n", gtphub_plane_idx_names[plane_idx]); if (!(what & BSC_FD_READ)) return 0; struct gtphub *hub = from_sgsns_ofd->data; static uint8_t buf[4096]; struct osmo_sockaddr from_addr; struct osmo_sockaddr to_addr; struct osmo_fd *to_ofd; int len; uint8_t *reply_buf; len = gtphub_read(from_sgsns_ofd, &from_addr, buf, sizeof(buf)); if (len < 1) return 0; len = gtphub_handle_buf(hub, GTPH_SIDE_SGSN, plane_idx, &from_addr, buf, len, gtphub_now(), &reply_buf, &to_ofd, &to_addr); if (len < 1) return 0; return gtphub_write(to_ofd, &to_addr, reply_buf, len); } static int from_ggsns_read_cb(struct osmo_fd *from_ggsns_ofd, unsigned int what) { unsigned int plane_idx = from_ggsns_ofd->priv_nr; OSMO_ASSERT(plane_idx < GTPH_PLANE_N); LOG(LOGL_DEBUG, "=== reading from GGSN (%s)\n", gtphub_plane_idx_names[plane_idx]); if (!(what & BSC_FD_READ)) return 0; struct gtphub *hub = from_ggsns_ofd->data; static uint8_t buf[4096]; struct osmo_sockaddr from_addr; struct osmo_sockaddr to_addr; struct osmo_fd *to_ofd; int len; uint8_t *reply_buf; len = gtphub_read(from_ggsns_ofd, &from_addr, buf, sizeof(buf)); if (len < 1) return 0; len = gtphub_handle_buf(hub, GTPH_SIDE_GGSN, plane_idx, &from_addr, buf, len, gtphub_now(), &reply_buf, &to_ofd, &to_addr); if (len < 1) return 0; return gtphub_write(to_ofd, &to_addr, reply_buf, len); } static int gtphub_unmap(struct gtphub *hub, struct gtp_packet_desc *p, struct gtphub_peer_port *from, struct gtphub_peer_port *to_proxy, struct gtphub_peer_port **final_unmapped, struct gtphub_peer_port **unmapped_from_seq) { /* Always (try to) unmap sequence and TEI numbers, which need to be * replaced in the packet. Either way, give precedence to the proxy, if * configured. */ if (unmapped_from_seq) *unmapped_from_seq = NULL; if (final_unmapped) *final_unmapped = NULL; p->tun = NULL; struct gtphub_peer_port *from_seq = NULL; struct gtphub_peer_port *from_tei = NULL; struct gtphub_peer_port *unmapped = NULL; from_seq = gtphub_unmap_seq(p, from); if (gtphub_unmap_header_tei(&from_tei, &p->tun, hub, p, from) < 0) return -1; struct gtphub_peer *from_peer = from->peer_addr->peer; if (from_seq && from_tei && (from_seq != from_tei)) { LOG(LOGL_DEBUG, "Seq unmap and TEI unmap yield two different peers." " Using seq unmap." " (from %s %s: seq %d yields %s, tei %u yields %s)\n", gtphub_plane_idx_names[p->plane_idx], gtphub_peer_str(from_peer), (int)p->seq, gtphub_port_str(from_seq), (unsigned int)p->header_tei_rx, gtphub_port_str2(from_tei) ); } unmapped = (from_seq? from_seq : from_tei); if (unmapped && to_proxy && (unmapped != to_proxy)) { LOG(LOGL_NOTICE, "Unmap yields a different peer than the configured proxy." " Using proxy." " unmapped: %s proxy: %s\n", gtphub_port_str(unmapped), gtphub_port_str2(to_proxy) ); } unmapped = (to_proxy? to_proxy : unmapped); if (!unmapped) { /* Return no error, but returned pointers are all NULL. */ return 0; } if (unmapped_from_seq) *unmapped_from_seq = from_seq; if (final_unmapped) *final_unmapped = unmapped; return 0; } static int gsn_addr_to_sockaddr(struct gsn_addr *src, uint16_t port, struct osmo_sockaddr *dst) { return osmo_sockaddr_init_udp(dst, gsn_addr_to_str(src), port); } /* If p is an Echo request, replace p's data with the matching response and * return 1. If p is no Echo request, return 0, or -1 if an invalid packet is * detected. */ static int gtphub_handle_echo_req(struct gtphub *hub, struct gtp_packet_desc *p, uint8_t **reply_buf) { if (p->type != GTP_ECHO_REQ) return 0; static uint8_t echo_response_data[14] = { 0x32, /* GTP v1 flags */ GTP_ECHO_RSP, 0x00, 14 - 8, /* Length in network byte order */ 0x00, 0x00, 0x00, 0x00, /* Zero TEI */ 0, 0, /* Seq, to be replaced */ 0, 0, /* no extensions */ 0x0e, /* Recovery IE */ 0 /* Restart counter, to be replaced */ }; uint16_t *seq = (uint16_t*)&echo_response_data[8]; uint8_t *recovery = &echo_response_data[13]; *seq = hton16(p->seq); *recovery = hub->restart_counter; *reply_buf = echo_response_data; return sizeof(echo_response_data); } struct gtphub_peer_port *gtphub_known_addr_have_port(const struct gtphub_bind *bind, const struct osmo_sockaddr *addr); /* Parse buffer as GTP packet, replace elements in-place and return the ofd and * address to forward to. Return a pointer to the osmo_fd, but copy the * sockaddr to *to_addr. The reason for this is that the sockaddr may expire at * any moment, while the osmo_fd is guaranteed to persist. Return the number of * bytes to forward, 0 or less on failure. */ int gtphub_handle_buf(struct gtphub *hub, unsigned int side_idx, unsigned int plane_idx, const struct osmo_sockaddr *from_addr, uint8_t *buf, size_t received, time_t now, uint8_t **reply_buf, struct osmo_fd **to_ofd, struct osmo_sockaddr *to_addr) { struct gtphub_bind *from_bind = &hub->to_gsns[side_idx][plane_idx]; struct gtphub_bind *to_bind = &hub->to_gsns[other_side_idx(side_idx)][plane_idx]; rate_ctr_add(&from_bind->counters_io->ctr[GTPH_CTR_BYTES_IN], received); LOG(LOGL_DEBUG, "%s rx %s from %s %s\n", (side_idx == GTPH_SIDE_GGSN)? "<-" : "->", gtphub_plane_idx_names[plane_idx], gtphub_side_idx_names[side_idx], osmo_sockaddr_to_str(from_addr)); struct gtp_packet_desc p; gtp_decode(buf, received, side_idx, plane_idx, &p, now); if (p.rc <= 0) { LOG(LOGL_ERROR, "INVALID: dropping GTP packet from %s %s %s\n", gtphub_side_idx_names[side_idx], gtphub_plane_idx_names[plane_idx], osmo_sockaddr_to_str(from_addr)); return -1; } rate_ctr_inc(&from_bind->counters_io->ctr[GTPH_CTR_PKTS_IN]); int reply_len; reply_len = gtphub_handle_echo_req(hub, &p, reply_buf); if (reply_len > 0) { /* It was an echo. Nothing left to do. */ osmo_sockaddr_copy(to_addr, from_addr); *to_ofd = &from_bind->ofd; rate_ctr_inc(&from_bind->counters_io->ctr[GTPH_CTR_PKTS_OUT]); rate_ctr_add(&from_bind->counters_io->ctr[GTPH_CTR_BYTES_OUT], reply_len); LOG(LOGL_DEBUG, "%s Echo response to %s: %d bytes to %s\n", (side_idx == GTPH_SIDE_GGSN)? "-->" : "<--", gtphub_side_idx_names[side_idx], (int)reply_len, osmo_sockaddr_to_str(to_addr)); return reply_len; } if (reply_len < 0) return -1; *to_ofd = &to_bind->ofd; /* If a proxy is configured, check that it's indeed that proxy talking * to us. A proxy is a forced 1:1 connection, e.g. to another gtphub, * so no-one else is allowed to talk to us from that side. */ struct gtphub_peer_port *from_peer = hub->proxy[side_idx][plane_idx]; if (from_peer) { if (osmo_sockaddr_cmp(&from_peer->sa, from_addr) != 0) { LOG(LOGL_ERROR, "Rejecting: %s proxy configured, but GTP packet" " received on %s bind is from another sender:" " proxy: %s sender: %s\n", gtphub_side_idx_names[side_idx], gtphub_side_idx_names[side_idx], gtphub_port_str(from_peer), osmo_sockaddr_to_str(from_addr)); return -1; } } if (!from_peer) { /* Find or create a peer with a matching address. The sender's * port may in fact differ. */ from_peer = gtphub_known_addr_have_port(from_bind, from_addr); } /* If any PDP context has been created, we already have an entry for * this GSN. If we don't have an entry, a GGSN has nothing to tell us * about, while an SGSN may initiate a PDP context. */ if (!from_peer) { if (side_idx == GTPH_SIDE_GGSN) { LOG(LOGL_ERROR, "Dropping packet: unknown GGSN peer: %s\n", osmo_sockaddr_to_str(from_addr)); return -1; } else { /* SGSN */ /* A new peer. If this is on the Ctrl plane, an SGSN * may make first contact without being known yet, so * create the peer struct for the current sender. */ if (plane_idx != GTPH_PLANE_CTRL) { LOG(LOGL_ERROR, "Dropping packet: User plane peer was not" "announced by PDP Context: %s\n", osmo_sockaddr_to_str(from_addr)); return -1; } struct gsn_addr from_gsna; uint16_t from_port; if (gsn_addr_from_sockaddr(&from_gsna, &from_port, from_addr) != 0) return -1; from_peer = gtphub_port_have(hub, from_bind, &from_gsna, from_port); } } if (!from_peer) { /* This could theoretically happen for invalid address data or * somesuch. */ LOG(LOGL_ERROR, "Dropping packet: invalid %s peer: %s\n", gtphub_side_idx_names[side_idx], osmo_sockaddr_to_str(from_addr)); return -1; } rate_ctr_add(&from_peer->counters_io->ctr[GTPH_CTR_BYTES_IN], received); rate_ctr_inc(&from_peer->counters_io->ctr[GTPH_CTR_PKTS_IN]); LOG(LOGL_DEBUG, "from %s peer: %s\n", gtphub_side_idx_names[side_idx], gtphub_port_str(from_peer)); struct gtphub_peer_port *to_peer_from_seq; struct gtphub_peer_port *to_peer; if (gtphub_unmap(hub, &p, from_peer, hub->proxy[other_side_idx(side_idx)][plane_idx], &to_peer, &to_peer_from_seq) != 0) { return -1; } if (p.tun) { struct gtphub_tunnel_endpoint *te = &p.tun->endpoint[p.side_idx][p.plane_idx]; rate_ctr_add(&te->counters_io->ctr[GTPH_CTR_BYTES_IN], received); rate_ctr_inc(&te->counters_io->ctr[GTPH_CTR_PKTS_IN]); } if ((!to_peer) && (side_idx == GTPH_SIDE_SGSN)) { if (gtphub_resolve_ggsn(hub, &p, &to_peer) < 0) return -1; } if (!to_peer) { LOG(LOGL_ERROR, "No %s to send to. Dropping packet.\n", gtphub_side_idx_names[other_side_idx(side_idx)]); return -1; } if (plane_idx == GTPH_PLANE_CTRL) { /* This may be a Create PDP Context response. If it is, there * are other addresses in the GTP message to set up apart from * the sender. */ if (gtphub_handle_pdp_ctx(hub, &p, from_peer, to_peer) != 0) return -1; } /* Either to_peer was resolved from an existing tunnel, * or a PDP Ctx and thus a tunnel has just been created, * or the tunnel has been deleted due to this message. */ OSMO_ASSERT(p.tun || (p.type == GTP_DELETE_PDP_RSP)); gtphub_check_restart_counter(hub, &p, from_peer); gtphub_map_restart_counter(hub, &p); /* If the GGSN is replying to an SGSN request, the sequence nr has * already been unmapped above (to_peer_from_seq != NULL), and we need not * create a new mapping. */ if (!to_peer_from_seq) gtphub_map_seq(&p, from_peer, to_peer); osmo_sockaddr_copy(to_addr, &to_peer->sa); *reply_buf = (uint8_t*)p.data; if (received) { rate_ctr_inc(&to_bind->counters_io->ctr[GTPH_CTR_PKTS_OUT]); rate_ctr_add(&to_bind->counters_io->ctr[GTPH_CTR_BYTES_OUT], received); rate_ctr_inc(&to_peer->counters_io->ctr[GTPH_CTR_PKTS_OUT]); rate_ctr_add(&to_peer->counters_io->ctr[GTPH_CTR_BYTES_OUT], received); } if (p.tun) { struct gtphub_tunnel_endpoint *te = &p.tun->endpoint[other_side_idx(p.side_idx)][p.plane_idx]; rate_ctr_inc(&te->counters_io->ctr[GTPH_CTR_PKTS_OUT]); rate_ctr_add(&te->counters_io->ctr[GTPH_CTR_BYTES_OUT], received); } LOG(LOGL_DEBUG, "%s Forward to %s: %d bytes to %s\n", (side_idx == GTPH_SIDE_SGSN)? "-->" : "<--", gtphub_side_idx_names[other_side_idx(side_idx)], (int)received, osmo_sockaddr_to_str(to_addr)); return received; } static void resolved_gssn_del_cb(struct expiring_item *expi) { struct gtphub_resolved_ggsn *ggsn; ggsn = container_of(expi, struct gtphub_resolved_ggsn, expiry_entry); gtphub_port_ref_count_dec(ggsn->peer); llist_del(&ggsn->entry); ggsn->expiry_entry.del_cb = 0; expiring_item_del(&ggsn->expiry_entry); talloc_free(ggsn); } void gtphub_resolved_ggsn(struct gtphub *hub, const char *apn_oi_str, struct gsn_addr *resolved_addr, time_t now) { struct gtphub_peer_port *pp; struct gtphub_resolved_ggsn *ggsn; LOG(LOGL_DEBUG, "Resolved GGSN callback: %s %s\n", apn_oi_str, osmo_hexdump((unsigned char*)resolved_addr, sizeof(*resolved_addr))); pp = gtphub_port_have(hub, &hub->to_gsns[GTPH_SIDE_GGSN][GTPH_PLANE_CTRL], resolved_addr, 2123); if (!pp) { LOG(LOGL_ERROR, "Internal: Cannot create/find peer '%s'\n", gsn_addr_to_str(resolved_addr)); return; } ggsn = talloc_zero(osmo_gtphub_ctx, struct gtphub_resolved_ggsn); OSMO_ASSERT(ggsn); INIT_LLIST_HEAD(&ggsn->entry); expiring_item_init(&ggsn->expiry_entry); ggsn->peer = pp; gtphub_port_ref_count_inc(pp); strncpy(ggsn->apn_oi_str, apn_oi_str, sizeof(ggsn->apn_oi_str)); ggsn->apn_oi_str[sizeof(ggsn->apn_oi_str) - 1] = '\0'; ggsn->expiry_entry.del_cb = resolved_gssn_del_cb; expiry_add(&hub->expire_slowly, &ggsn->expiry_entry, now); llist_add(&ggsn->entry, &hub->resolved_ggsns); } static int gtphub_gc_peer_port(struct gtphub_peer_port *pp) { return pp->ref_count == 0; } static int gtphub_gc_peer_addr(struct gtphub_peer_addr *pa) { struct gtphub_peer_port *pp, *npp; llist_for_each_entry_safe(pp, npp, &pa->ports, entry) { if (gtphub_gc_peer_port(pp)) { LOG(LOGL_DEBUG, "expired: peer %s\n", gtphub_port_str(pp)); gtphub_peer_port_del(pp); } } return llist_empty(&pa->ports); } static int gtphub_gc_peer(struct gtphub_peer *p) { struct gtphub_peer_addr *pa, *npa; llist_for_each_entry_safe(pa, npa, &p->addresses, entry) { if (gtphub_gc_peer_addr(pa)) { gtphub_peer_addr_del(pa); } } /* Note that there's a ref_count in each gtphub_peer_port instance * listed within p->addresses, referenced by TEI mappings from * hub->tei_map. As long as those don't expire, this peer will stay. */ return llist_empty(&p->addresses) && nr_map_empty(&p->seq_map); } static void gtphub_gc_bind(struct gtphub_bind *b) { struct gtphub_peer *p, *n; llist_for_each_entry_safe(p, n, &b->peers, entry) { if (gtphub_gc_peer(p)) { gtphub_peer_del(p); } } } void gtphub_gc(struct gtphub *hub, time_t now) { int expired; expired = expiry_tick(&hub->expire_quickly, now); expired += expiry_tick(&hub->expire_slowly, now); /* ... */ if (expired) { int s, p; for_each_side_and_plane(s, p) { gtphub_gc_bind(&hub->to_gsns[s][p]); } } } static void gtphub_gc_cb(void *data) { struct gtphub *hub = data; gtphub_gc(hub, gtphub_now()); osmo_timer_schedule(&hub->gc_timer, GTPH_GC_TICK_SECONDS, 0); } static void gtphub_gc_start(struct gtphub *hub) { hub->gc_timer.cb = gtphub_gc_cb; hub->gc_timer.data = hub; osmo_timer_schedule(&hub->gc_timer, GTPH_GC_TICK_SECONDS, 0); } /* called by unit tests */ void gtphub_init(struct gtphub *hub) { gtphub_zero(hub); INIT_LLIST_HEAD(&hub->tunnels); INIT_LLIST_HEAD(&hub->pending_deletes); expiry_init(&hub->expire_quickly, GTPH_EXPIRE_QUICKLY_SECS); expiry_init(&hub->expire_slowly, GTPH_EXPIRE_SLOWLY_MINUTES * 60); nr_pool_init(&hub->tei_pool, 1, 0xffffffff); int side_idx; int plane_idx; for_each_side_and_plane(side_idx, plane_idx) { gtphub_bind_init(&hub->to_gsns[side_idx][plane_idx]); } hub->to_gsns[GTPH_SIDE_SGSN][GTPH_PLANE_CTRL].label = "SGSN Ctrl"; hub->to_gsns[GTPH_SIDE_GGSN][GTPH_PLANE_CTRL].label = "GGSN Ctrl"; hub->to_gsns[GTPH_SIDE_SGSN][GTPH_PLANE_USER].label = "SGSN User"; hub->to_gsns[GTPH_SIDE_GGSN][GTPH_PLANE_USER].label = "GGSN User"; } /* For the test suite, this is kept separate from gtphub_stop(), which also * closes sockets. The test suite avoids using sockets and would cause * segfaults when trying to close uninitialized ofds. */ void gtphub_free(struct gtphub *hub) { /* By expiring all mappings, a garbage collection should free * everything else. A gtphub_bind_free() will assert that everything is * indeed empty. */ expiry_clear(&hub->expire_quickly); expiry_clear(&hub->expire_slowly); int side_idx; int plane_idx; for_each_side_and_plane(side_idx, plane_idx) { gtphub_gc_bind(&hub->to_gsns[side_idx][plane_idx]); gtphub_bind_free(&hub->to_gsns[side_idx][plane_idx]); } } void gtphub_stop(struct gtphub *hub) { int side_idx; int plane_idx; for_each_side_and_plane(side_idx, plane_idx) { gtphub_bind_stop(&hub->to_gsns[side_idx][plane_idx]); } gtphub_free(hub); } static int gtphub_make_proxy(struct gtphub *hub, struct gtphub_peer_port **pp, struct gtphub_bind *bind, const struct gtphub_cfg_addr *addr) { if (!addr->addr_str) return 0; struct gsn_addr gsna; if (gsn_addr_from_str(&gsna, addr->addr_str) != 0) return -1; *pp = gtphub_port_have(hub, bind, &gsna, addr->port); /* This is *the* proxy. Make sure it is never expired. */ gtphub_port_ref_count_inc(*pp); return 0; } int gtphub_start(struct gtphub *hub, struct gtphub_cfg *cfg, uint8_t restart_counter) { gtphub_init(hub); hub->restart_counter = restart_counter; hub->sgsn_use_sender = cfg->sgsn_use_sender? 1 : 0; /* If a Ctrl plane proxy is configured, ares will never be used. */ if (!cfg->proxy[GTPH_SIDE_GGSN][GTPH_PLANE_CTRL].addr_str) { if (gtphub_ares_init(hub) != 0) { LOG(LOGL_FATAL, "Failed to initialize ares\n"); return -1; } } int side_idx; int plane_idx; for_each_side_and_plane(side_idx, plane_idx) { int rc; rc = gtphub_bind_start(&hub->to_gsns[side_idx][plane_idx], &cfg->to_gsns[side_idx][plane_idx], (side_idx == GTPH_SIDE_SGSN) ? from_sgsns_read_cb : from_ggsns_read_cb, hub, plane_idx); if (rc) { LOG(LOGL_FATAL, "Failed to bind for %ss (%s)\n", gtphub_side_idx_names[side_idx], gtphub_plane_idx_names[plane_idx]); return rc; } } for_each_side_and_plane(side_idx, plane_idx) { if (gtphub_make_proxy(hub, &hub->proxy[side_idx][plane_idx], &hub->to_gsns[side_idx][plane_idx], &cfg->proxy[side_idx][plane_idx]) != 0) { LOG(LOGL_FATAL, "Cannot configure %s proxy" " %s port %d.\n", gtphub_side_idx_names[side_idx], cfg->proxy[side_idx][plane_idx].addr_str, (int)cfg->proxy[side_idx][plane_idx].port); return -1; } } for_each_side_and_plane(side_idx, plane_idx) { if (hub->proxy[side_idx][plane_idx]) LOG(LOGL_NOTICE, "Using %s %s proxy %s\n", gtphub_side_idx_names[side_idx], gtphub_plane_idx_names[plane_idx], gtphub_port_str(hub->proxy[side_idx][plane_idx])); } if (hub->sgsn_use_sender) LOG(LOGL_NOTICE, "Using sender address and port for SGSN instead of GSN Addr IE and default ports.\n"); gtphub_gc_start(hub); return 0; } static struct gtphub_peer_addr *gtphub_peer_find_addr(const struct gtphub_peer *peer, const struct gsn_addr *addr) { struct gtphub_peer_addr *a; llist_for_each_entry(a, &peer->addresses, entry) { if (gsn_addr_same(&a->addr, addr)) return a; } return NULL; } static struct gtphub_peer_port *gtphub_addr_find_port(const struct gtphub_peer_addr *a, uint16_t port) { OSMO_ASSERT(port); struct gtphub_peer_port *pp; llist_for_each_entry(pp, &a->ports, entry) { if (pp->port == port) return pp; } return NULL; } static struct gtphub_peer_addr *gtphub_addr_find(const struct gtphub_bind *bind, const struct gsn_addr *addr) { struct gtphub_peer *peer; llist_for_each_entry(peer, &bind->peers, entry) { struct gtphub_peer_addr *a = gtphub_peer_find_addr(peer, addr); if (a) return a; } return NULL; } static struct gtphub_peer_port *gtphub_port_find(const struct gtphub_bind *bind, const struct gsn_addr *addr, uint16_t port) { struct gtphub_peer_addr *a = gtphub_addr_find(bind, addr); if (!a) return NULL; return gtphub_addr_find_port(a, port); } struct gtphub_peer_port *gtphub_port_find_sa(const struct gtphub_bind *bind, const struct osmo_sockaddr *addr) { struct gsn_addr gsna; uint16_t port; gsn_addr_from_sockaddr(&gsna, &port, addr); return gtphub_port_find(bind, &gsna, port); } static struct gtphub_peer *gtphub_peer_new(struct gtphub *hub, struct gtphub_bind *bind) { struct gtphub_peer *peer = talloc_zero(osmo_gtphub_ctx, struct gtphub_peer); OSMO_ASSERT(peer); INIT_LLIST_HEAD(&peer->addresses); nr_pool_init(&peer->seq_pool, 0, 0xffff); nr_map_init(&peer->seq_map, &peer->seq_pool, &hub->expire_quickly); /* TODO use something random to pick the initial sequence nr. 0x6d31 produces the ASCII character sequence 'm1', currently used in gtphub_nc_test.sh. */ peer->seq_pool.last_nr = 0x6d31 - 1; llist_add(&peer->entry, &bind->peers); return peer; } static struct gtphub_peer_addr *gtphub_peer_add_addr(struct gtphub_peer *peer, const struct gsn_addr *addr) { struct gtphub_peer_addr *a; a = talloc_zero(osmo_gtphub_ctx, struct gtphub_peer_addr); OSMO_ASSERT(a); a->peer = peer; gsn_addr_copy(&a->addr, addr); INIT_LLIST_HEAD(&a->ports); llist_add(&a->entry, &peer->addresses); return a; } static struct gtphub_peer_addr *gtphub_addr_have(struct gtphub *hub, struct gtphub_bind *bind, const struct gsn_addr *addr) { struct gtphub_peer_addr *a = gtphub_addr_find(bind, addr); if (a) return a; /* If we haven't found an address, that means we need to create an * entirely new peer for the new address. More addresses may be added * to this peer later, but not via this function. */ struct gtphub_peer *peer = gtphub_peer_new(hub, bind); a = gtphub_peer_add_addr(peer, addr); LOG(LOGL_DEBUG, "New peer address: %s %s\n", bind->label, gsn_addr_to_str(&a->addr)); return a; } static struct gtphub_peer_port *gtphub_addr_add_port(struct gtphub_peer_addr *a, uint16_t port) { struct gtphub_peer_port *pp; pp = talloc_zero(osmo_gtphub_ctx, struct gtphub_peer_port); OSMO_ASSERT(pp); pp->peer_addr = a; pp->port = port; pp->last_restart_count = -1; if (gsn_addr_to_sockaddr(&a->addr, port, &pp->sa) != 0) { talloc_free(pp); return NULL; } pp->counters_io = rate_ctr_group_alloc(osmo_gtphub_ctx, >phub_ctrg_io_desc, 0); llist_add(&pp->entry, &a->ports); LOG(LOGL_DEBUG, "New peer port: %s port %d\n", gsn_addr_to_str(&a->addr), (int)port); return pp; } struct gtphub_peer_port *gtphub_port_have(struct gtphub *hub, struct gtphub_bind *bind, const struct gsn_addr *addr, uint16_t port) { struct gtphub_peer_addr *a = gtphub_addr_have(hub, bind, addr); struct gtphub_peer_port *pp = gtphub_addr_find_port(a, port); if (pp) return pp; return gtphub_addr_add_port(a, port); } /* Find a GGSN peer with a matching address. If the address is known but the * port not, create a new port for that peer address. */ struct gtphub_peer_port *gtphub_known_addr_have_port(const struct gtphub_bind *bind, const struct osmo_sockaddr *addr) { struct gtphub_peer_addr *pa; struct gtphub_peer_port *pp; struct gsn_addr gsna; uint16_t port; gsn_addr_from_sockaddr(&gsna, &port, addr); pa = gtphub_addr_find(bind, &gsna); if (!pa) return NULL; pp = gtphub_addr_find_port(pa, port); if (!pp) pp = gtphub_addr_add_port(pa, port); return pp; } /* Return 0 if the message in p is not applicable for GGSN resolution, -1 if * resolution should be possible but failed, and 1 if resolution was * successful. *pp will be set to NULL if <1 is returned. */ static int gtphub_resolve_ggsn(struct gtphub *hub, struct gtp_packet_desc *p, struct gtphub_peer_port **pp) { *pp = NULL; /* TODO determine from message type whether IEs should be present? */ int rc; const char *imsi_str; rc = get_ie_imsi_str(p->ie, 0, &imsi_str); if (rc < 1) return rc; OSMO_ASSERT(imsi_str); const char *apn_str; rc = get_ie_apn_str(p->ie, &apn_str); if (rc < 1) return rc; OSMO_ASSERT(apn_str); *pp = gtphub_resolve_ggsn_addr(hub, imsi_str, apn_str); return (*pp)? 1 : -1; } /* TODO move to osmocom/core/socket.c ? */ /* use this in osmo_sock_init() to remove dup. */ /* Internal: call getaddrinfo for osmo_sockaddr_init(). The caller is required to call freeaddrinfo(*result), iff zero is returned. */ static int _osmo_getaddrinfo(struct addrinfo **result, uint16_t family, uint16_t type, uint8_t proto, const char *host, uint16_t port) { struct addrinfo hints; char portbuf[16]; sprintf(portbuf, "%u", port); memset(&hints, '\0', sizeof(struct addrinfo)); hints.ai_family = family; if (type == SOCK_RAW) { /* Workaround for glibc, that returns EAI_SERVICE (-8) if * SOCK_RAW and IPPROTO_GRE is used. */ hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; } else { hints.ai_socktype = type; hints.ai_protocol = proto; } return getaddrinfo(host, portbuf, &hints, result); } /* TODO move to osmocom/core/socket.c ? */ int osmo_sockaddr_init(struct osmo_sockaddr *addr, uint16_t family, uint16_t type, uint8_t proto, const char *host, uint16_t port) { struct addrinfo *res; int rc; rc = _osmo_getaddrinfo(&res, family, type, proto, host, port); if (rc != 0) { LOG(LOGL_ERROR, "getaddrinfo returned error %d\n", (int)rc); return -EINVAL; } OSMO_ASSERT(res->ai_addrlen <= sizeof(addr->a)); memcpy(&addr->a, res->ai_addr, res->ai_addrlen); addr->l = res->ai_addrlen; freeaddrinfo(res); return 0; } int osmo_sockaddr_to_strs(char *addr_str, size_t addr_str_len, char *port_str, size_t port_str_len, const struct osmo_sockaddr *addr, int flags) { int rc; if ((addr->l < 1) || (addr->l > sizeof(addr->a))) { LOGP(DGTPHUB, LOGL_ERROR, "Invalid address size: %d\n", addr->l); return -1; } if (addr->l > sizeof(addr->a)) { LOGP(DGTPHUB, LOGL_ERROR, "Invalid address: too long: %d\n", addr->l); return -1; } rc = getnameinfo((struct sockaddr*)&addr->a, addr->l, addr_str, addr_str_len, port_str, port_str_len, flags); if (rc) LOGP(DGTPHUB, LOGL_ERROR, "Invalid address: %s: %s\n", gai_strerror(rc), osmo_hexdump((uint8_t*)&addr->a, addr->l)); return rc; } const char *osmo_sockaddr_to_strb(const struct osmo_sockaddr *addr, char *buf, size_t buf_len) { const int portbuf_len = 6; OSMO_ASSERT(buf_len > portbuf_len); char *portbuf = buf + buf_len - portbuf_len; buf_len -= portbuf_len; if (osmo_sockaddr_to_strs(buf, buf_len, portbuf, portbuf_len, addr, NI_NUMERICHOST | NI_NUMERICSERV)) return NULL; char *pos = buf + strnlen(buf, buf_len-1); size_t len = buf_len - (pos - buf); snprintf(pos, len, " port %s", portbuf); buf[buf_len-1] = '\0'; return buf; } const char *osmo_sockaddr_to_str(const struct osmo_sockaddr *addr) { static char buf[256]; const char *result = osmo_sockaddr_to_strb(addr, buf, sizeof(buf)); if (! result) return "(invalid)"; return result; } int osmo_sockaddr_cmp(const struct osmo_sockaddr *a, const struct osmo_sockaddr *b) { if (a == b) return 0; if (!a) return -1; if (!b) return 1; if (a->l != b->l) { /* Lengths are not the same, but determine the order. Will * anyone ever sort a list by osmo_sockaddr though...? */ int cmp = memcmp(&a->a, &b->a, (a->l < b->l)? a->l : b->l); if (cmp == 0) { if (a->l < b->l) return -1; else return 1; } return cmp; } return memcmp(&a->a, &b->a, a->l); } void osmo_sockaddr_copy(struct osmo_sockaddr *dst, const struct osmo_sockaddr *src) { OSMO_ASSERT(src->l <= sizeof(dst->a)); memcpy(&dst->a, &src->a, src->l); dst->l = src->l; }