gtphub: monitor GSNs' restart counters.

If a GSN indicates that it has reset, tear down each known tunnel for that GSN
individually (don't send the GSNs on the other side a different restart
counter, because they represent more than just this GSN).

Sponsored-by: On-Waves ehi
diff --git a/openbsc/src/gprs/gtphub.c b/openbsc/src/gprs/gtphub.c
index 971603a..66716de 100644
--- a/openbsc/src/gprs/gtphub.c
+++ b/openbsc/src/gprs/gtphub.c
@@ -1362,21 +1362,8 @@
 	return NULL;
 }
 
-
-static void gtphub_check_restart_counter(struct gtphub *hub,
-					 struct gtp_packet_desc *p,
-					 struct gtphub_peer_port *from)
-{
-	/* TODO */
-	/* 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. */
-}
-
 static void gtphub_map_restart_counter(struct gtphub *hub,
-				       struct gtp_packet_desc *p,
-				       struct gtphub_peer_port *from,
-				       struct gtphub_peer_port *to)
+				       struct gtp_packet_desc *p)
 {
 	if (p->rc != GTP_RC_PDU_C)
 		return;
@@ -1416,14 +1403,15 @@
 		    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: %" PRIx32 " -> %" PRIx32 " (to %s)\n",
-	    gtphub_port_str(from_port), p->header_tei_rx, unmapped_tei,
-	    gtphub_port_str2(to->peer));
+	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;
@@ -1764,6 +1752,117 @@
 	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)
+{
+	struct gtphub_tunnel *tun;
+	llist_for_each_entry(tun, &hub->tunnels, entry) {
+		int side_idx;
+		for_each_side(side_idx) {
+			if (pp != tun->endpoint[side_idx][GTPH_PLANE_CTRL].peer)
+				continue;
+
+			/* 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;
@@ -2094,7 +2193,7 @@
 	}
 
 	gtphub_check_restart_counter(hub, &p, from_peer);
-	gtphub_map_restart_counter(hub, &p, from_peer, to_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
@@ -2497,6 +2596,7 @@
 	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);