aboutsummaryrefslogtreecommitdiffstats
path: root/openbsc/src/gprs/gtphub.c
diff options
context:
space:
mode:
authorNeels Hofmeyr <nhofmeyr@sysmocom.de>2015-11-24 13:31:06 +0100
committerNeels Hofmeyr <nhofmeyr@sysmocom.de>2015-12-03 11:40:03 +0100
commite54cd1555a874b132116b1269264de7d6cc3d24d (patch)
treee508fd06bc542dd6f67abe698f55b8690a4aa683 /openbsc/src/gprs/gtphub.c
parent2c8b58139f3e3c29309dd6518f77a62414de560c (diff)
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
Diffstat (limited to 'openbsc/src/gprs/gtphub.c')
-rw-r--r--openbsc/src/gprs/gtphub.c349
1 files changed, 297 insertions, 52 deletions
diff --git a/openbsc/src/gprs/gtphub.c b/openbsc/src/gprs/gtphub.c
index 4200822aa..60c3cc191 100644
--- a/openbsc/src/gprs/gtphub.c
+++ b/openbsc/src/gprs/gtphub.c
@@ -86,9 +86,11 @@ struct gtp_packet_desc {
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;
time_t timestamp;
union gtpie_member *ie[GTPIE_SIZE];
};
@@ -269,7 +271,8 @@ void validate_gtp0_header(struct gtp_packet_desc *p)
p->type = ntoh8(pheader->type);
p->seq = ntoh16(pheader->seq);
- p->header_tei = 0; /* TODO */
+ 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;
@@ -311,7 +314,8 @@ void validate_gtp1_header(struct gtp_packet_desc *p)
}
p->type = ntoh8(pheader->type);
- p->header_tei = ntoh32(pheader->tei);
+ p->header_tei_rx = ntoh32(pheader->tei);
+ p->header_tei = p->header_tei_rx;
p->seq = ntoh16(pheader->seq);
LOG(LOGL_DEBUG, "GTPv1\n"
@@ -323,7 +327,7 @@ void validate_gtp1_header(struct gtp_packet_desc *p)
"| next = %" PRIu8 " 0x%02" PRIx8 "\n",
p->type, p->type,
ntoh16(pheader->length), ntoh16(pheader->length),
- p->header_tei, p->header_tei,
+ p->header_tei_rx, p->header_tei_rx,
p->seq, p->seq,
pheader->npdu, pheader->npdu,
pheader->next, pheader->next);
@@ -473,6 +477,7 @@ static int get_ie_apn_str(union gtpie_member *ie[], const char **apn_str)
* 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)
@@ -480,6 +485,7 @@ static void gtp_decode(const uint8_t *data, int data_len,
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;
@@ -897,12 +903,14 @@ static int gtphub_read(const struct osmo_fd *from,
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--;
}
@@ -934,6 +942,128 @@ static struct nr_mapping *gtphub_mapping_new()
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=%x / U %x=%x)",
+ c->tei_orig, c->tei_repl,
+ u->tei_orig, u->tei_repl);
+ 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("%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;
+ int side_idx;
+ int plane_idx;
+ for (side_idx = 0; side_idx < GTPH_SIDE_N; side_idx++) {
+ for (plane_idx = 0; plane_idx < GTPH_PLANE_N; plane_idx++) {
+ struct gtphub_tunnel_endpoint *te =
+ &tun->endpoint[side_idx][plane_idx];
+ if (!(te->peer && te->tei_orig && te->tei_repl))
+ 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 (side_idx = 0; side_idx < GTPH_SIDE_N; side_idx++) {
+ for (plane_idx = 0; plane_idx < GTPH_PLANE_N; plane_idx++) {
+ /* clear ref count */
+ gtphub_tunnel_endpoint_set_peer(
+ &tun->endpoint[side_idx][plane_idx], NULL);
+ }
+ }
+
+ 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);
+
+ 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)
{
@@ -985,6 +1115,7 @@ static const char *gtphub_port_str2(struct gtphub_peer_port *port)
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,
@@ -1031,22 +1162,6 @@ static struct nr_mapping *gtphub_mapping_have(struct nr_map *map,
return nrm;
}
-static uint32_t gtphub_tei_mapping_have(struct gtphub *hub,
- int plane_idx,
- struct gtphub_peer_port *from,
- uint32_t orig_tei,
- time_t now)
-{
- struct nr_mapping *nrm = gtphub_mapping_have(&hub->tei_map[plane_idx],
- from, orig_tei, now);
- LOG(LOGL_DEBUG, "New %s TEI: (from %s, TEI %u) <-- TEI %u\n",
- gtphub_plane_idx_names[plane_idx],
- gtphub_port_str(from),
- (unsigned int)orig_tei, (unsigned int)nrm->repl);
-
- return (uint32_t)nrm->repl;
-}
-
static void gtphub_map_seq(struct gtp_packet_desc *p,
struct gtphub_peer_port *from_port,
struct gtphub_peer_port *to_port)
@@ -1077,6 +1192,100 @@ static struct gtphub_peer_port *gtphub_unmap_seq(struct gtp_packet_desc *p,
return nrm->origin;
}
+static struct gtphub_tunnel *gtphub_tun_find(struct gtphub *hub,
+ int side_idx,
+ int plane_idx,
+ struct gtphub_peer_port *from,
+ uint32_t tei_orig,
+ uint32_t tei_repl)
+{
+ OSMO_ASSERT(from);
+
+ struct gtphub_tunnel *tun;
+ /* TODO: optimize: don't iterate *all* tunnels. */
+ llist_for_each_entry(tun, &hub->tunnels, entry) {
+ struct gtphub_tunnel_endpoint *te =
+ &tun->endpoint[side_idx][plane_idx];
+ if (((!tei_orig) || (te->tei_orig == tei_orig))
+ && ((!tei_repl) || (te->tei_repl == tei_repl))
+ && gsn_addr_same(&te->peer->peer_addr->addr, &from->peer_addr->addr))
+ return tun;
+ }
+ return NULL;
+}
+
+static void gtphub_expire_reused_tei(struct gtphub *hub,
+ int side_idx,
+ int plane_idx,
+ struct gtphub_peer_port *from,
+ uint32_t tei_orig,
+ struct gtphub_tunnel *except)
+{
+ struct gtphub_tunnel *exists, *e2;
+
+ llist_for_each_entry_safe(exists, e2, &hub->tunnels, entry) {
+ if (exists == except)
+ continue;
+
+ struct gtphub_tunnel_endpoint *te =
+ &exists->endpoint[side_idx][plane_idx];
+ if ((te->tei_orig == tei_orig)
+ && gsn_addr_same(&te->peer->peer_addr->addr,
+ &from->peer_addr->addr)) {
+
+ /* 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:"
+ " peer %s sent %s TEI %x,"
+ " previously used by tunnel %s...\n",
+ gtphub_port_str(from),
+ gtphub_plane_idx_names[plane_idx],
+ tei_orig,
+ gtphub_tunnel_str(exists));
+ LOG(LOGL_NOTICE, "...while establishing tunnel %s\n",
+ gtphub_tunnel_str(except));
+ expiring_item_del(&exists->expiry_entry);
+ /* continue to find more matches. There shouldn't be
+ * any, but let's make sure. */
+ }
+ }
+}
+
+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)
+{
+ 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 ((te_to->tei_repl == p->header_tei_rx)
+ && gsn_addr_same(&te_from->peer->peer_addr->addr,
+ &from->peer_addr->addr)) {
+ gtphub_tunnel_refresh(hub, tun, p->timestamp);
+ return te_to;
+ }
+ }
+ return NULL;
+}
+
+
static void gtphub_check_restart_counter(struct gtphub *hub,
struct gtp_packet_desc *p,
struct gtphub_peer_port *from)
@@ -1106,30 +1315,28 @@ static int gtphub_unmap_header_tei(struct gtphub_peer_port **to_port_p,
/* 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. */
- uint32_t tei = p->header_tei;
- if (!tei)
+ if (!p->header_tei_rx)
return 0;
/* to_peer has previously announced a TEI, which was stored and
- * mapped in from_peer's tei_map. */
- struct nr_mapping *nrm;
- nrm = nr_map_get_inv(&hub->tei_map[p->plane_idx], tei);
- if (!nrm) {
- LOG(LOGL_ERROR, "Received unknown TEI %" PRIu32 " from %s\n",
- tei, gtphub_port_str(from_port));
+ * mapped in a tunnel struct. */
+ struct gtphub_tunnel_endpoint *to;
+ to = gtphub_unmap_tei(hub, p, from_port);
+ if (!to) {
+ LOG(LOGL_ERROR, "Received unknown TEI %" PRIx32 " from %s\n",
+ p->header_tei_rx, gtphub_port_str(from_port));
return -1;
}
- struct gtphub_peer_port *to_port = nrm->origin;
- uint32_t unmapped_tei = nrm->orig;
+ uint32_t unmapped_tei = to->tei_orig;
set_tei(p, unmapped_tei);
- LOG(LOGL_DEBUG, "Unmapped TEI coming from %s: %u -> %u (to %s)\n",
- gtphub_port_str(from_port),
- (unsigned int)tei, (unsigned int)unmapped_tei,
- gtphub_port_str2(to_port));
+ LOG(LOGL_DEBUG, "Unmapped TEI coming from %s: %" PRIx32 " -> %" PRIx32 " (to %s)\n",
+ gtphub_port_str(from_port), p->header_tei_rx, unmapped_tei,
+ gtphub_port_str2(to->peer));
+
+ *to_port_p = to->peer;
- *to_port_p = to_port;
return 0;
}
@@ -1140,6 +1347,8 @@ static int gtphub_unmap_header_tei(struct gtphub_peer_port **to_port_p,
static int gtphub_handle_pdp_ctx_ies(struct gtphub *hub,
struct gtphub_bind from_bind_arr[],
struct gtphub_bind to_bind_arr[],
+ struct gtphub_peer_port *sgsn_ctrl,
+ struct gtphub_peer_port *ggsn_ctrl,
struct gtp_packet_desc *p)
{
OSMO_ASSERT(p->plane_idx == GTPH_PLANE_CTRL);
@@ -1162,15 +1371,41 @@ static int gtphub_handle_pdp_ctx_ies(struct gtphub *hub,
osmo_static_assert((GTPH_PLANE_CTRL == 0) && (GTPH_PLANE_USER == 1),
plane_nrs_match_GSN_addr_IE_indices);
+ struct gtphub_tunnel *tun = NULL;
+
+ if (p->type == GTP_CREATE_PDP_REQ) {
+ /* A new tunnel. */
+ tun = gtphub_tunnel_new();
+ llist_add(&tun->entry, &hub->tunnels);
+ gtphub_tunnel_refresh(hub, tun, p->timestamp);
+ gtphub_tunnel_endpoint_set_peer(&tun->endpoint[GTPH_SIDE_GGSN][GTPH_PLANE_CTRL],
+ ggsn_ctrl);
+ } else if (p->type == GTP_CREATE_PDP_RSP) {
+ /* Find the tunnel created during request */
+ OSMO_ASSERT(sgsn_ctrl);
+ tun = gtphub_tun_find(hub, other_side_idx(p->side_idx),
+ p->plane_idx, sgsn_ctrl, 0, p->header_tei_rx);
+
+ if (!tun) {
+ LOG(LOGL_ERROR, "Create PDP Context Response: cannot"
+ " find matching request from SGSN %s, mapped TEI %x.\n",
+ gtphub_port_str(sgsn_ctrl), p->header_tei_rx);
+ return -1;
+ }
+ }
+
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++) {
struct gsn_addr addr_from_ie;
uint32_t tei_from_ie;
int ie_idx;
- /* Fetch GSN Address and TEI from IEs */
+ /* 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(&addr_from_ie, p, plane_idx);
if (rc) {
LOG(LOGL_ERROR, "Cannot read %s GSN Address IE\n",
@@ -1203,14 +1438,20 @@ static int gtphub_handle_pdp_ctx_ies(struct gtphub *hub,
&addr_from_ie,
gtphub_plane_idx_default_port[plane_idx]);
+ gtphub_tunnel_endpoint_set_peer(&tun->endpoint[side_idx][plane_idx],
+ peer_from_ie);
+
if (tei_from_ie) {
/* Create TEI mapping and replace in GTP packet IE */
- uint32_t mapped_tei =
- gtphub_tei_mapping_have(hub, plane_idx,
- peer_from_ie,
- tei_from_ie,
- p->timestamp);
+ uint32_t mapped_tei = nr_pool_next(&hub->tei_pool[plane_idx]);
+
+ tun->endpoint[side_idx][plane_idx].tei_orig = tei_from_ie;
+ tun->endpoint[side_idx][plane_idx].tei_repl = mapped_tei;
p->ie[ie_idx]->tv4.v = hton32(mapped_tei);
+
+ gtphub_expire_reused_tei(hub, side_idx, plane_idx,
+ peer_from_ie, tei_from_ie,
+ tun);
}
/* Replace the GSN address to reflect gtphub. */
@@ -1223,6 +1464,14 @@ static int gtphub_handle_pdp_ctx_ies(struct gtphub *hub,
}
}
+ 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_REQ) {
+ LOG(LOGL_DEBUG, "New tunnel: %s\n",
+ gtphub_tunnel_str(tun));
+ }
+
return 0;
}
@@ -1319,7 +1568,7 @@ static int gtphub_unmap(struct gtphub *hub,
gtphub_peer_str(from_peer),
(int)p->seq,
gtphub_port_str(from_seq),
- (unsigned int)p->header_tei,
+ (unsigned int)p->header_tei_rx,
gtphub_port_str2(from_tei)
);
}
@@ -1341,9 +1590,6 @@ static int gtphub_unmap(struct gtphub *hub,
return 0;
}
- LOG(LOGL_DEBUG, "from seq %p; from tei %p; unmapped => %p\n",
- from_seq, from_tei, unmapped);
-
if (unmapped_from_seq)
*unmapped_from_seq = from_seq;
if (unmapped_from_tei)
@@ -1420,7 +1666,7 @@ int gtphub_from_ggsns_handle_buf(struct gtphub *hub,
osmo_sockaddr_to_str(from_addr));
static struct gtp_packet_desc p;
- gtp_decode(buf, received, plane_idx, &p, now);
+ gtp_decode(buf, received, GTPH_SIDE_GGSN, plane_idx, &p, now);
if (p.rc <= 0)
return -1;
@@ -1502,7 +1748,7 @@ int gtphub_from_ggsns_handle_buf(struct gtphub *hub,
* are other addresses in the GTP message to set up apart from
* the sender. */
if (gtphub_handle_pdp_ctx_ies(hub, from_bind_arr, to_bind_arr,
- &p)
+ sgsn, ggsn, &p)
!= 0)
return -1;
}
@@ -1585,7 +1831,7 @@ int gtphub_from_sgsns_handle_buf(struct gtphub *hub,
osmo_sockaddr_to_str(from_addr));
static struct gtp_packet_desc p;
- gtp_decode(buf, received, plane_idx, &p, now);
+ gtp_decode(buf, received, GTPH_SIDE_SGSN, plane_idx, &p, now);
if (p.rc <= 0)
return -1;
@@ -1684,11 +1930,11 @@ int gtphub_from_sgsns_handle_buf(struct gtphub *hub,
}
if (plane_idx == GTPH_PLANE_CTRL) {
- /* This may be a Create PDP Context requst. If it is, there are
+ /* This may be a Create PDP Context request. If it is, there are
* other addresses in the GTP message to set up apart from the
* sender. */
if (gtphub_handle_pdp_ctx_ies(hub, from_bind_arr, to_bind_arr,
- &p)
+ sgsn, ggsn, &p)
!= 0)
return -1;
}
@@ -1848,15 +2094,14 @@ void gtphub_init(struct gtphub *hub)
{
gtphub_zero(hub);
+ INIT_LLIST_HEAD(&hub->tunnels);
+
expiry_init(&hub->expire_quickly, GTPH_EXPIRE_QUICKLY_SECS);
expiry_init(&hub->expire_slowly, GTPH_EXPIRE_SLOWLY_MINUTES * 60);
int plane_idx;
for (plane_idx = 0; plane_idx < GTPH_PLANE_N; plane_idx++) {
nr_pool_init(&hub->tei_pool[plane_idx], 1, 0xffffffff);
- nr_map_init(&hub->tei_map[plane_idx],
- &hub->tei_pool[plane_idx],
- &hub->expire_slowly);
gtphub_bind_init(&hub->to_ggsns[plane_idx]);
gtphub_bind_init(&hub->to_sgsns[plane_idx]);